Skip to content
AWS aws networking 7 min read

NAT Gateway & NAT Instances

Servers in a private subnet have no public IP address, so the internet cannot reach them directly. But those servers still need to reach out to the internet for things like downloading software updates, calling a third-party API, or pulling a container image. NAT (Network Address Translation) solves this: it lets instances start outbound connections to the internet while staying completely unreachable from the inbound side. AWS gives you two ways to do this — a fully managed NAT Gateway, or a do-it-yourself NAT instance — and the difference between them is mostly about money, effort, and how much you value high availability.

What NAT actually does

NAT means Network Address Translation: a device rewrites the source IP address of outgoing traffic so that replies come back to the right place. In a VPC (Virtual Private Cloud, your own isolated network in AWS), a private instance with an address like 10.0.2.15 cannot talk to the internet because that address is not routable on the public internet. A NAT device sits in a public subnet, has a public address, and translates the private instance’s traffic to use that public address on the way out. Return traffic comes back to the NAT device, which translates it back to the private instance.

The key property is direction: NAT only allows connections that the private instance initiates. Nobody on the internet can open a new connection to your private instance through NAT. That is exactly what you want for back-end servers, databases, and worker nodes that should never accept inbound public traffic.

NAT is for outbound-only internet access. If you need inbound public access (for example, a web server users connect to), put that resource in a public subnet behind an Internet Gateway or a load balancer instead.

When to use this (and when not to)

  • Use NAT when private instances need outbound internet — OS patches, pip/npm/apt installs, calling external APIs like Stripe or OpenAI, pushing logs to a SaaS tool.
  • Do not use NAT if your traffic is to other AWS services (S3, DynamoDB, ECR, etc.). For those, a VPC endpoint keeps traffic on the AWS network and avoids NAT data-processing charges entirely. This is one of the biggest cost savings people miss.
  • Do not use NAT if the instance genuinely needs to be publicly reachable inbound — that belongs in a public subnet.

NAT Gateway vs NAT instance

A NAT Gateway is a managed AWS service: you create it, attach an Elastic IP (a permanent public IP address you own in your account), point your private route table at it, and AWS handles everything else — scaling, patching, and failover within an Availability Zone (AZ, a physically separate datacenter within a region). A NAT instance is just a regular EC2 (Elastic Compute Cloud) virtual machine running NAT software that you launch, configure, patch, and babysit.

NAT GatewayNAT instanceVPC endpoint (for AWS services)
Managed byAWSYouAWS
High availabilityPer-AZ, auto-managedYou build it yourselfHighly available by design
BandwidthScales to 100 Gbps automaticallyLimited by instance sizeN/A
Cost modelPer hour + per GB processedEC2 instance hours onlyMostly cheap / free for gateway type
Maintenance effortNonePatching, monitoring, scalingNone
Security groupsNot supported (use NACLs/routes)Yes (it’s an EC2 instance)Yes
Best forProduction, most workloadsTiny dev/test, very cost-sensitiveS3, DynamoDB, ECR, etc.

For almost all production workloads, use a NAT Gateway. Choose a NAT instance only when you are highly cost-conscious in a non-critical environment and are willing to maintain it.

The cost gotcha you must understand

A NAT Gateway bills two ways at once:

  1. An hourly charge just for existing (roughly $0.045/hour in us-east-1, about $32/month).
  2. A per-GB data-processing charge on every gigabyte that flows through it (roughly $0.045/GB).

That second charge is the classic cost leak. A chatty private workload — say a fleet of containers constantly pulling images or streaming logs to the internet — can push terabytes through the NAT Gateway and rack up a surprising bill. Worse, a NAT Gateway lives in a single AZ. If that AZ fails, instances in other AZs lose internet. For true high availability you deploy one NAT Gateway per AZ and route each subnet’s traffic to the gateway in its own AZ. That multiplies both the hourly and per-GB costs.

Cost trap: routing S3 or ECR traffic through a NAT Gateway means you pay per-GB processing for traffic that could be free over a VPC endpoint. For container-heavy or data-heavy workloads, add an S3 gateway endpoint and ECR interface endpoints before you do anything else.

Create a NAT Gateway — console

  1. Open the VPC console and choose NAT gateways in the left menu.
  2. Click Create NAT gateway.
  3. Give it a Name (for example, nat-az1).
  4. For Subnet, choose a public subnet (one whose route table sends 0.0.0.0/0 to an Internet Gateway). The NAT Gateway must live in a public subnet.
  5. For Connectivity type, leave it as Public.
  6. Under Elastic IP allocation ID, click Allocate Elastic IP to attach a public IP.
  7. Click Create NAT gateway and wait until its state becomes Available (about 1-2 minutes).
  8. Now update the private subnet’s route table: go to Route tables, select the private route table, Edit routes, and add a route for destination 0.0.0.0/0 with target = your new NAT Gateway.

Create a NAT Gateway — AWS CLI

First allocate an Elastic IP, then create the gateway in a public subnet, then add the route.

# 1. Allocate an Elastic IP
aws ec2 allocate-address --domain vpc

# 2. Create the NAT Gateway in a PUBLIC subnet
aws ec2 create-nat-gateway \
  --subnet-id subnet-0a1b2c3d \
  --allocation-id eipalloc-0a1b2c3d \
  --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=nat-az1}]'

# 3. Point the PRIVATE route table at the NAT Gateway
aws ec2 create-route \
  --route-table-id rtb-0a1b2c3d \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id nat-0a1b2c3d4e5f

Output:

{
    "NatGateway": {
        "NatGatewayId": "nat-0a1b2c3d4e5f",
        "SubnetId": "subnet-0a1b2c3d",
        "VpcId": "vpc-0a1b2c3d",
        "State": "pending",
        "NatGatewayAddresses": [
            {
                "AllocationId": "eipalloc-0a1b2c3d",
                "PublicIp": "54.210.10.20"
            }
        ]
    }
}

It takes a minute or two to move from pending to available.

High availability with one NAT Gateway per AZ (Terraform)

This snippet creates a NAT Gateway in each AZ and gives each private subnet its own default route, so losing one AZ does not take down the others.

resource "aws_eip" "nat" {
  count  = 2
  domain = "vpc"
}

resource "aws_nat_gateway" "this" {
  count         = 2
  allocation_id = aws_eip.nat[count.index].id
  subnet_id     = aws_subnet.public[count.index].id
  tags          = { Name = "nat-az${count.index + 1}" }
}

resource "aws_route" "private_default" {
  count                  = 2
  route_table_id         = aws_route_table.private[count.index].id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.this[count.index].id
}

Running a NAT instance instead

A NAT instance is a normal EC2 instance using an AMI (Amazon Machine Image, a prebuilt OS template) configured to forward traffic. The two things that trip people up:

  • You must disable the source/destination check on the instance, because by default EC2 drops packets whose source or destination is not the instance itself — and NAT does exactly that kind of forwarding.
  • You must give it a security group allowing inbound traffic from your private subnets and outbound to the internet.
aws ec2 modify-instance-attribute \
  --instance-id i-0a1b2c3d4e5f \
  --no-source-dest-check

Then point your private route table’s 0.0.0.0/0 route at the instance’s network interface (eni-...) instead of a NAT Gateway. Remember you now own all patching, monitoring, and failover for that box.

Best Practices

  • Use a managed NAT Gateway for production; reserve NAT instances for tiny, cost-sensitive, non-critical environments.
  • For real high availability, deploy one NAT Gateway per AZ and route each subnet to the gateway in its own AZ.
  • Add VPC endpoints for S3, DynamoDB, and ECR so that AWS-bound traffic skips the NAT Gateway and its per-GB charge.
  • Watch the per-GB data-processing cost with CloudWatch and Cost Explorer — chatty workloads can quietly run up large bills.
  • Never expect NAT to provide inbound access; it is outbound-only by design. Use an Internet Gateway or load balancer for inbound.
  • Tag and name your NAT Gateways and Elastic IPs clearly so per-AZ resources are easy to track and clean up.
Last updated June 15, 2026
Was this helpful?