In standard AWS Virtual Private Cloud (VPC) architectures, EC2 instances, ECS tasks, and EKS Kubernetes pods run inside private subnets without public IPv4 addresses. To reach external endpoints—including AWS's own public regional APIs (`s3.us-east-1.amazonaws.com`, `ecr.dkr.us-east-1.amazonaws.com`, `logs.us-east-1.amazonaws.com`)—traffic is routed by default through a managed **AWS NAT Gateway** residing in a public subnet.
Because AWS NAT Gateways charge a mandatory **$0.045 per GB Data Processing Fee** ($45.00 per TB) on top of hourly instance fees—even when communicating with an S3 bucket in the exact same region and account—unconfigured VPCs routinely waste thousands of dollars per month processing internal storage traffic through NAT Gateways.
Anatomy of the AWS NAT Gateway Cost Structure
A highly available 3-Availability-Zone VPC deploying one managed NAT Gateway per AZ incurs three compounding cost layers:
If an EKS cluster or Spark data pipeline reads and writes 120 TB/month to an S3 bucket in the same region without a VPC Endpoint, S3 itself charges $0.00/GB for same-region transfer—yet the NAT Gateway bills **$5,400.00/month** ($120,000\text{ GB} \times \$0.045$) simply for translating private IP packets to S3's public IP range.
Gateway VPC Endpoints vs. Interface VPC Endpoints (PrivateLink)
AWS provides two distinct mechanisms to bypass NAT Gateways and keep traffic on the private AWS network fabric:
| Specification | Gateway VPC Endpoint | Interface VPC Endpoint (PrivateLink) |
|---|---|---|
| Supported AWS Services | Amazon S3 & Amazon DynamoDB Only | 100+ Services (ECR, CloudWatch Logs, STS, KMS, SQS, Kinesis, S3) |
| Network Mechanism | VPC Route Table Prefix List (`pl-63a5400a`) | Elastic Network Interface (ENI) with Private IP + Split-Horizon DNS |
| Hourly Endpoint Fee | $0.00 / hour (100% Free) | $0.01 / hour per AZ ($7.30/mo per AZ) |
| Per-GB Data Processing Fee | $0.00 / GB (100% Free) | $0.01 / GB (First 1 PB/mo) |
| Cross-VPC / On-Prem VPN Reachable | No (Strictly local VPC route tables) | Yes (Reachable via Direct Connect, VPN, and Transit Gateway) |
Breakeven Math for Interface VPC Endpoints (ECR, CloudWatch Logs, STS)
While S3 and DynamoDB Gateway Endpoints are 100% free and should be provisioned in every VPC on Day 1, **Interface VPC Endpoints** (used to pull Docker container images from Amazon ECR or ship logs to CloudWatch without hitting the NAT Gateway) cost **$0.01/hour per Availability Zone** ($21.90/month for a 3-AZ deployment) plus **$0.01/GB** processed (saving $0.035/GB compared to the NAT Gateway's $0.045/GB fee).
Important ECR Container Pull Rule: When Kubernetes nodes pull Docker image layers from Amazon ECR, the small manifest authentication call hits `ecr.api` and `ecr.dkr`, but the actual multi-gigabyte container layer blobs are stored in an AWS-managed **S3 bucket**! Therefore, simply adding the free **S3 Gateway VPC Endpoint** eliminates 98% of ECR container pull NAT charges without even needing the paid `ecr.dkr` Interface Endpoint.
Production Terraform HCL: Free S3 & DynamoDB Gateway Endpoints
Attach the following Terraform resources to all private route tables in your VPC to immediately drop S3 and DynamoDB NAT Gateway processing charges to `$0.00`:
resource "aws_vpc_endpoint" "s3_gateway" {
vpc_id = aws_vpc.production.id
service_name = "com.amazonaws.${var.aws_region}.s3"
vpc_endpoint_type = "Gateway"
# Attach to all Private Subnet Route Tables
route_table_ids = aws_route_table.private[*].id
tags = {
Name = "prod-s3-gateway-endpoint"
CostCenter = "zero-nat-bypass"
}
}
resource "aws_vpc_endpoint" "dynamodb_gateway" {
vpc_id = aws_vpc.production.id
service_name = "com.amazonaws.${var.aws_region}.dynamodb"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.private[*].id
tags = {
Name = "prod-dynamodb-gateway-endpoint"
CostCenter = "zero-nat-bypass"
}
}
Auditing NAT Gateway Traffic Leaks with VPC Flow Logs
If your NAT Gateway `BytesOutToDestination` CloudWatch metric remains high after deploying Gateway Endpoints, enable v5 VPC Flow Logs (`${interface-id} ${srcaddr} ${dstaddr} ${pkt-srcaddr} ${pkt-dstaddr} ${bytes}`) on the NAT Gateway Elastic Network Interface (`eni-0a8b...`) and execute this CloudWatch Logs Insights query to rank the top destination IPv4 prefixes billing `$0.045/GB`:
# CloudWatch Logs Insights Query: Top Destination IPs Traversing NAT Gateway ENI filter interfaceId = "eni-0a8b9412c7e102941" and action = "ACCEPT" | stats sum(bytes)/1000000000 as Total_GB by pktDstAddr | sort Total_GB desc | limit 5 # Sample Diagnostic Output (Before Attaching S3 Route Table Entry to Worker Subnet 3): # pktDstAddr Total_GB Service Resolution (via ip-ranges.json) # 52.217.104.198 4,812.40 AMAZON S3 (us-east-1) -> Missed Private Route Table! # 54.239.28.85 1,120.15 AMAZON DYNAMODB (us-east-1) -> Missed Private Route Table! # 3.218.180.12 184.20 External SaaS Telemetry Ingest
In the audit above, one newly created EKS worker node subnet (`private-subnet-us-east-1c-gpu`) had been associated with a secondary VPC route table that lacked the `vpce-0192...` prefix-list entry—causing `5,932 GB` of local S3 and DynamoDB traffic to silently route through the NAT Gateway for `$266.96` in avoidable processing fees.
Audit Small-File Object Storage API Costs Next
Learn why storing millions of 4 KB files inflates Class A PUT and LIST request invoices by 10x+.