Why AWS NAT Gateway Must Reside in a Public Subnet: Architecture Deep Dive


3 views

Many AWS newcomers get confused about NAT Gateway placement because the distinction between public and private subnets isn't always clear-cut. Let's break down the technical reasoning:

// Typical VPC setup with NAT (CloudFormation snippet)
Resources:
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
  
  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.2.0/24
  
  NatGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !Ref EIP
      SubnetId: !Ref PublicSubnet  // Critical placement

A NAT Gateway needs internet access to function - it's essentially translating private IP traffic to a public IP. The traffic flow looks like this:

  1. Private instance → NAT Gateway (public subnet)
  2. NAT Gateway → Internet Gateway
  3. Internet Gateway → Public internet

While the NAT Gateway resides in a public subnet, it's protected by AWS's security model:

  • Only outbound traffic is allowed (stateful)
  • No inbound initiation from internet
  • Managed by AWS (unlike NAT instances)

Here's how you'd implement this in Terraform:

resource "aws_nat_gateway" "example" {
  allocation_id = aws_eip.example.id
  subnet_id     = aws_subnet.public.id  # Must be public

  tags = {
    Name = "NAT GW Public Subnet"
  }
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.example.id

  route {
    cidr_block = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.example.id
  }
}

Watch for these mistakes:

Error Result
NAT in private subnet Connectivity timeout
No IGW route in public subnet NAT cannot reach internet
Wrong route table association Private instances bypass NAT

In AWS networking, the placement of NAT Gateways often causes confusion. While many engineers assume NAT Gateways should reside in private subnets, AWS documentation consistently recommends placing them in public subnets. Let's break down why this architecture makes sense.

First, let's clarify the core concepts:


Public Subnet:
- Has a route to an Internet Gateway (IGW) in its route table
- Can initiate outbound internet connections
- Can receive inbound connections from the internet

Private Subnet:
- No direct route to an IGW
- Cannot initiate outbound internet connections directly
- Cannot receive inbound connections from the internet

The NAT Gateway needs internet access to function - it's essentially translating private IP addresses to a public one for outbound connections. Here's the technical rationale:

  1. Internet Access Requirement: NAT Gateway needs to communicate with the internet to forward traffic from private instances
  2. Route Table Configuration: Public subnets have the 0.0.0.0/0 route pointing to the IGW
  3. Security: NAT Gateway itself shouldn't be directly accessible from the internet, but needs outbound access

Here's how to properly set up a NAT Gateway in a public subnet using AWS CDK (TypeScript):


import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';

export class NatGatewayStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Create VPC with public and private subnets
    const vpc = new ec2.Vpc(this, 'VPC', {
      natGateways: 1,
      subnetConfiguration: [
        {
          name: 'Public',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          name: 'Private',
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        }
      ]
    });

    // The NAT Gateway is automatically placed in the public subnet
    // and private subnets are configured to route through it
  }
}

When working with NAT Gateways, watch out for these pitfalls:

  • Placing NAT Gateway in a private subnet (won't have internet access)
  • Forgetting to allocate elastic IPs for the NAT Gateway
  • Not updating private subnet route tables to point to the NAT Gateway
  • Using NAT Instances instead of NAT Gateways without proper security group rules

NAT Gateways in public subnets offer several advantages:

Metric Public Subnet NAT Private Subnet NAT
Throughput Up to 45 Gbps Limited by additional hops
Availability Highly available (AWS managed) Depends on custom setup
Cost Standard pricing Additional routing costs

While NAT Gateways reside in public subnets, they're still secure:


- NAT Gateways don't accept inbound connections from the internet
- They only allow outbound connections initiated from private instances
- AWS manages the security aspects of the NAT Gateway service
- No security groups are applied to NAT Gateways (unlike NAT Instances)

While the public subnet placement is standard, there are exceptions:

NAT Instances: If you must use EC2 instances as NAT devices (instead of the managed NAT Gateway service), you'll need to:

  1. Place them in public subnets
  2. Configure proper security groups
  3. Disable source/destination checks

PrivateLink Endpoints: For accessing AWS services, consider VPC endpoints to avoid NAT Gateway costs entirely.