The NAT Gateway Tax: How AWS Charges $1M/Year for Traffic That Never Leaves Its Network (And the Free Fix)
CloudCostChefs TeamChef's Cost Recipe: The NAT Gateway Tax
Every byte your private subnets push to S3, DynamoDB, or CloudWatch through a NAT Gateway costs you $0.045/GB — for traffic that never leaves the AWS network. The fix is free. The only cost is the time you've been ignoring it.
The $1M/Year Tax You're Paying by Default
Here's how the NAT Gateway tax works: you deploy a VPC with private subnets (as you should for security). Your applications need to reach AWS services — S3 for storage, DynamoDB for data, CloudWatch for logging, ECR for container images. The default path? All that traffic routes through a NAT Gateway.
AWS charges $0.045 per GB processed through NAT Gateways, plus $0.045/hour (~$32.40/month) just to keep one running. For a busy production account pushing 2TB/day to AWS services through NAT, that's roughly $2,700/month in data processing alone — $32K/year per NAT Gateway, for traffic that never touches the public internet.
Scale that across multiple VPCs, multiple AZs (each needing its own NAT Gateway for HA), and multiple AWS accounts? You're looking at six figures annually — sometimes seven — in charges for routing internal AWS traffic the expensive way.
The NAT Gateway Triple Charge
Where Your NAT Gateway Money Actually Goes
When teams audit their NAT Gateway traffic, they consistently find the same pattern: 60–80% of traffic is hitting AWS services that support VPC endpoints. That's money being burned on a middleman that serves no purpose.
Only that last category — actual internet-bound traffic — actually needs a NAT Gateway. Everything else can be routed through VPC endpoints at a fraction of the cost or completely free.
The Fix: VPC Endpoints (Most of It Is Free)
AWS offers two types of VPC endpoints that let your private subnets reach AWS services without routing through a NAT Gateway:
Gateway Endpoints
$0
- Available for S3 and DynamoDB
- No hourly charge
- No per-GB data processing charge
- Route table entry — no ENI needed
- Traffic stays on AWS backbone
Interface Endpoints (PrivateLink)
~$7.30/mo per AZ
- Available for 180+ AWS services
- $0.01/hour + $0.01/GB processed
- 78% cheaper per-GB than NAT Gateway
- Creates an ENI in your subnet
- Supports CloudWatch, ECR, SQS, SNS, KMS, and more
The Math: NAT Gateway vs. VPC Endpoints
For a production account processing 1TB/month of S3 traffic through a single NAT Gateway:
Via NAT Gateway
- Hourly: $32.40/month
- Data processing: 1,024 GB x $0.045 = $46.08/month
- Total: $78.48/month
- Annual: $941.76/year
Via Gateway Endpoint (S3)
- Hourly: $0.00/month
- Data processing: $0.00/month
- Total: $0.00/month
- Annual: $0.00/year
Now scale that to a real enterprise: 10 VPCs, 3 AZs each, processing 5TB/month of S3 and DynamoDB traffic:
$101K
Annual cost via NAT Gateways
30 NAT Gateways x ($32.40 + $230.40)/month
$0
Annual cost via Gateway Endpoints
10 Gateway Endpoints (one per VPC) — free
Step 1: Audit Your NAT Gateway Traffic
Before you change anything, you need to see where your NAT Gateway money is going. Here's the exact recipe:
Enable VPC Flow Logs
Enable Flow Logs on each VPC or on the NAT Gateway's ENI specifically. Send logs to S3 (cheaper for bulk analysis) or CloudWatch Logs (easier for quick queries).
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids vpc-0abc123def456 \
--traffic-type ALL \
--log-destination-type s3 \
--log-destination arn:aws:s3:::my-flow-logs-bucket \
--log-format '${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${bytes} ${pkt-srcaddr} ${pkt-dstaddr} ${action} ${tcp-flags}'Pro tip: Include pkt-srcaddr and pkt-dstaddr fields to identify the original source behind the NAT.
Identify NAT Gateway ENI
Find your NAT Gateway's network interface ID in the VPC console. Filter Flow Logs by this ENI to isolate NAT-routed traffic.
aws ec2 describe-nat-gateways \
--query 'NatGateways[*].{Id:NatGatewayId,ENI:NatGatewayAddresses[0].NetworkInterfaceId,State:State}' \
--output tableQuery & Group by Destination
Use Athena or CloudWatch Logs Insights to group traffic by destination IP ranges. AWS service endpoints use well-known IP prefixes published in the ip-ranges.json file.
-- Athena query: top destinations by bytes through NAT
SELECT dstaddr, SUM(bytes) as total_bytes,
COUNT(*) as flow_count
FROM vpc_flow_logs
WHERE interface_id = 'eni-0abc123' -- NAT ENI
GROUP BY dstaddr
ORDER BY total_bytes DESC
LIMIT 20;Cross-reference the top destination IPs with AWS's ip-ranges.json to identify which are S3, DynamoDB, or other AWS services.
Step 2: Deploy VPC Endpoints (Start with the Free Ones)
Deploy endpoints alongside your existing NAT Gateways — this is additive, not destructive. Traffic will automatically prefer the endpoint path once route tables are updated.
2aCreate S3 Gateway Endpoint (Free)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0abc123def456 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-private1 rtb-private2 rtb-private3Include ALL private subnet route tables. Missed route tables = traffic still going through NAT.
2bCreate DynamoDB Gateway Endpoint (Free)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0abc123def456 \
--service-name com.amazonaws.us-east-1.dynamodb \
--route-table-ids rtb-private1 rtb-private2 rtb-private3Same as S3 — completely free. If you use DynamoDB at all, there's zero reason not to deploy this today.
2cCreate Interface Endpoints for High-Traffic Services
For services like ECR, CloudWatch Logs, SQS, SNS, and KMS, deploy Interface Endpoints. At $0.01/GB vs. $0.045/GB, these pay for themselves quickly:
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0abc123def456 \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.ecr.dkr \
--subnet-ids subnet-private1 subnet-private2 \
--security-group-ids sg-endpoint-accessCommon high-value Interface Endpoints: ecr.dkr, ecr.api, logs (CloudWatch), monitoring, sqs, sns, kms, sts, ssm.
Step 3: Validate the Traffic Shift
After deploying endpoints, confirm traffic is actually flowing through them and no longer through the NAT Gateway.
Check CloudWatch NAT metrics
Monitor BytesOutToDestination and BytesInFromSource on each NAT Gateway. You should see a clear drop after endpoint deployment.
Re-run your Flow Log analysis
Run the same Athena query from Step 1c. S3 and DynamoDB destination IPs should disappear from NAT Gateway traffic.
Verify application health
Check application logs and metrics for any connectivity issues. Gateway Endpoints are transparent to applications, but Interface Endpoints may require DNS resolution changes (enable PrivateDnsEnabled).
Wait for the next billing cycle
Compare NAT Gateway data processing charges month-over-month in Cost Explorer. Filter by NatGateway usage type.
Step 4: Right-Size or Sunset NAT Gateways
Once endpoints handle AWS-service traffic, evaluate what's left on each NAT Gateway:
If remaining traffic is low (<100 GB/month)
Consider replacing the NAT Gateway with a t4g.nano NAT instance ($3.07/month vs. $32.40/month) for non-critical VPCs.
If no internet-bound traffic remains
Delete the NAT Gateway entirely. Some VPCs only needed NAT for reaching AWS services in the first place.
If traffic is high (>17 TB/month)
Evaluate AWS's provisioned NAT Gateway option, which offers fixed bandwidth pricing with free data processing for high-throughput workloads.
Consolidate across AZs
For dev/staging VPCs, consider a single NAT Gateway instead of one per AZ. The $0.01/GB cross-AZ charge may be cheaper than running multiple NAT Gateways at $32.40/month each.
Quick Wins Checklist: Do These Today
These actions take minutes, cost nothing, and start saving immediately:
Automate It: Terraform Example
Bake VPC endpoints into your infrastructure-as-code so every new VPC gets them by default:
# S3 Gateway Endpoint (FREE)
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.private[*].id
tags = {
Name = "${var.environment}-s3-endpoint"
ManagedBy = "terraform"
CostCenter = var.cost_center
}
}
# DynamoDB Gateway Endpoint (FREE)
resource "aws_vpc_endpoint" "dynamodb" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.dynamodb"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.private[*].id
tags = {
Name = "${var.environment}-dynamodb-endpoint"
ManagedBy = "terraform"
CostCenter = var.cost_center
}
}
# ECR Interface Endpoint (for container workloads)
resource "aws_vpc_endpoint" "ecr_dkr" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.ecr.dkr"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
private_dns_enabled = true
tags = {
Name = "${var.environment}-ecr-dkr-endpoint"
ManagedBy = "terraform"
CostCenter = var.cost_center
}
}Common Gotchas to Watch For
Missing route tables
Gateway Endpoints only work for route tables explicitly associated with them. If you have 6 private subnets across 3 AZs with different route tables, make sure all route tables are included.
S3 bucket policies with VPC conditions
If your S3 bucket policies restrict access by source IP (the NAT Gateway's Elastic IP), you'll need to update them to use aws:sourceVpce or aws:sourceVpc conditions instead.
Interface Endpoint security groups
Interface Endpoints need security groups that allow inbound HTTPS (443) from your private subnets. Forgetting this blocks all traffic through the endpoint.
Gateway Endpoints don't work cross-region or from on-prem
If you access S3 cross-region or via VPN/Direct Connect, you still need NAT Gateway or Interface Endpoint for S3. Gateway Endpoints are VPC-local only.
Endpoint policy restrictions
By default, VPC endpoints allow full access to the service. In regulated environments, use endpoint policies to restrict access to specific S3 buckets or DynamoDB tables.
Chef's Pro Tip
Don't rip out your NAT Gateways on day one. Deploy VPC endpoints alongside them, update route tables for specific services, validate with Flow Logs, then sunset the NAT when traffic drops. The whole migration takes a sprint, not a quarter.
Add Gateway Endpoints to your standard VPC Terraform module so every new VPC gets S3 and DynamoDB endpoints by default. This prevents the problem from ever recurring.
The Bottom Line
This isn't optimization. It's removing a cost that should never have existed. S3 and DynamoDB Gateway Endpoints are free, take minutes to deploy, and eliminate per-GB NAT charges entirely. Interface Endpoints for other AWS services cost 78% less than the NAT Gateway path.
For most production AWS environments, VPC endpoints eliminate 60–80% of NAT Gateway data processing charges — often translating to five or six figures annually. Your CFO will wonder why nobody did this sooner.
Start today: one S3 Gateway Endpoint per VPC. It costs nothing and pays for itself immediately.