Private workloads still need to pull packages and call APIs, and NAT data charges are a classic surprise on the bill.
Instances in private subnets have no route to the internet. For outbound-only access, put a NAT gateway in a public subnet and add 0.0.0.0/0 -> nat-... to the private subnets' route table. Return traffic flows back; nothing on the internet can open a connection in.
Design points:
- A NAT gateway lives in one AZ. For resilience create one per AZ, and route each AZ's private subnets to their local NAT, so an AZ failure doesn't cut egress elsewhere and you avoid cross-AZ data charges.
- You pay per hour and per GB processed. Heavy traffic through NAT (container image pulls, S3 transfers, logs) gets expensive.
- For IPv6, use an egress-only internet gateway instead: IPv6 addresses are globally unique, so no translation is needed.
- NAT instances (an EC2 doing NAT) are cheaper but you own their availability.
Reduce NAT traffic with VPC endpoints:
- Gateway endpoints for S3 and DynamoDB: free, added as routes (via a managed prefix list) to the route tables you choose. Traffic to those services never touches the NAT.
- Interface endpoints (PrivateLink) for most other services such as ECR, SQS, STS, Secrets Manager and CloudWatch Logs: an ENI with a private IP in your subnets, billed per hour and per GB but often cheaper than NAT for heavy use, and required for fully isolated subnets.
Endpoint policies can restrict what can be done through the endpoint, for example only buckets in your organization.
Example
EIP=$(aws ec2 allocate-address --domain vpc --query AllocationId --output text)
NAT=$(aws ec2 create-nat-gateway --subnet-id subnet-pub-a --allocation-id "$EIP" \
--query NatGateway.NatGatewayId --output text)
aws ec2 wait nat-gateway-available --nat-gateway-ids "$NAT"
# Private subnets in AZ a send internet-bound traffic to the local NAT
aws ec2 create-route --route-table-id rtb-priv-a \
--destination-cidr-block 0.0.0.0/0 --nat-gateway-id "$NAT"
# Free gateway endpoint: S3 traffic bypasses the NAT entirely
aws ec2 create-vpc-endpoint --vpc-id vpc-0a1b2c3d \
--service-name com.amazonaws.eu-west-1.s3 \
--route-table-ids rtb-priv-a rtb-priv-b
aws ec2 describe-route-tables --route-table-ids rtb-priv-a \
--query 'RouteTables[0].Routes[].[DestinationCidrBlock || DestinationPrefixListId, NatGatewayId || GatewayId]' \
--output text
# 10.20.0.0/16 local
# 0.0.0.0/0 nat-0a1b...
# pl-6da54004 vpce-0c2d...Remember
- NAT gateway = outbound-only internet for private subnets; one per AZ
- NAT is billed per hour and per GB processed
- Gateway endpoints for S3 and DynamoDB are free and skip the NAT
- Interface endpoints give private access to most other AWS APIs
Watch out for
- Pulling large container images or S3 data through a NAT gateway instead of using endpoints.