AWS NAT Gateway Costs Explained: Why Your Bill Is So High and How to Fix It
NAT Gateway is one of those AWS services that looks cheap on the pricing page and then quietly takes over your bill. Teams deploy one per AZ as a best practice, forget about it, and months later find $500-$2,000/month in NAT charges they never expected.
The problem isn’t the hourly rate. It’s the data processing charge that most people overlook during architecture planning. Every gigabyte that flows through a NAT Gateway gets billed, even traffic to other AWS services. That design decision you made in week one, putting everything in private subnets, can become your most expensive networking choice.
This guide breaks down exactly how NAT Gateway pricing works, why the bill surprises people, and the concrete steps to reduce it. Each optimization includes CLI commands you can run today.
How NAT Gateway Pricing Works
NAT Gateway has two billing components, and most people only plan for one.
Hourly charge: $0.045 per hour in us-east-1. That’s $32.85/month per NAT Gateway, running 24/7. This applies the moment the gateway is provisioned, whether traffic flows through it or not. Partial hours are billed as full hours.
Data processing charge: $0.045 per GB for every gigabyte processed. This applies to ALL traffic that passes through the gateway, in both directions. AWS-bound traffic, internet-bound traffic, it all counts.
These charges are on top of standard AWS data transfer fees. So when a private subnet instance downloads 1 GB from S3 through a NAT Gateway, you pay the NAT data processing fee ($0.045) plus any applicable data transfer charges.
Regional pricing varies significantly. In sa-east-1 (Sao Paulo), the hourly rate jumps to $0.093/hr and data processing to $0.093/GB. That’s more than double us-east-1. Always check pricing for your specific region.
Monthly baseline costs by deployment pattern (us-east-1):
| Setup | Monthly Hourly Cost | 100 GB Data Processing | Total |
|---|---|---|---|
| Single NAT Gateway | $32.85 | $4.50 | $37.35 |
| 2 AZs (HA) | $65.70 | $4.50 | $70.20 |
| 3 AZs (HA) | $98.55 | $4.50 | $103.05 |
At 1 TB/month of data processing, those numbers become $77.85, $110.70, and $143.55 respectively. At 10 TB, the data processing charge alone is $450.
Why NAT Gateway Bills Surprise People
The data processing charge is where the surprises live. Here are the patterns that catch teams off guard.
AWS Service Traffic Routed Through NAT
This is the number one cause of unexpected NAT costs. Private subnets that access S3, DynamoDB, ECR, CloudWatch, SQS, or any other AWS service through a NAT Gateway incur data processing charges on every byte.
A common scenario: your ECS containers pull images from ECR on every deployment. Each image might be 500 MB-2 GB. With 50 container tasks restarting daily, that’s 25-100 GB/month in ECR pulls alone, all flowing through NAT. Add S3 data operations, CloudWatch log shipping, and SQS polling, and you can easily hit 500 GB-1 TB/month of AWS service traffic through NAT.
None of this traffic needs to touch a NAT Gateway. VPC endpoints exist for exactly this purpose.
The Fixed Cost Trap at Low Traffic
At low data volumes, NAT Gateway’s effective cost per GB is brutal. If you process only 10 GB/month, your per-GB cost is roughly $3.33 (the $32.85 hourly charge divided across 10 GB, plus the $0.045 processing fee). That’s 74x more expensive than it appears on the pricing page.
Dev and staging environments are the worst offenders here. They often have full production network topology (multi-AZ NAT Gateways) but minimal traffic.
Cross-AZ Data Transfer
If resources in one AZ send traffic through a NAT Gateway in a different AZ, you pay cross-AZ data transfer fees ($0.01/GB each way) in addition to the NAT data processing charge. This is easy to misconfigure and hard to spot on the bill.
VPC Gateway Endpoints: The #1 Cost Saver (NAT-O001)
Gateway endpoints for S3 and DynamoDB are free. No hourly charge. No data processing charge. No bandwidth limits. There is genuinely no reason not to deploy them in every VPC that has a NAT Gateway.
When you route S3 traffic through a gateway endpoint instead of NAT, you eliminate the $0.045/GB data processing fee entirely. For a workload processing 1 TB/month of S3 traffic, that’s $45/month saved from a single route table entry.
Create a gateway endpoint for S3:
# Get your VPC ID and route table IDs
VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=isDefault,Values=false" \
--query 'Vpcs[0].VpcId' --output text)
ROUTE_TABLES=$(aws ec2 describe-route-tables \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'RouteTables[].RouteTableId' --output text | tr '\t' ',')
# Create S3 gateway endpoint
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids $ROUTE_TABLES
Create a gateway endpoint for DynamoDB:
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--service-name com.amazonaws.us-east-1.dynamodb \
--route-table-ids $ROUTE_TABLES
Verify your endpoints are active:
aws ec2 describe-vpc-endpoints \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'VpcEndpoints[].{Service:ServiceName,State:State,Type:VpcEndpointType}'
After creating gateway endpoints, the route tables are updated automatically. Traffic to S3 and DynamoDB from your private subnets will bypass the NAT Gateway with zero additional configuration on the application side.
CostPatrol’s NAT-O001 rule flags NAT Gateways with high data processing charges and cross-references your VPC configuration to check whether gateway endpoints are deployed. If S3 or DynamoDB traffic is flowing through NAT when it doesn’t need to, you’ll see it in the scan results.
Interface Endpoints for Other AWS Services
Beyond S3 and DynamoDB, most AWS services support interface endpoints (powered by AWS PrivateLink). These aren’t free, but they’re significantly cheaper than routing through NAT for high-traffic services.
Interface endpoint pricing: $0.01/hr per AZ ($7.30/month) plus $0.01/GB data processed.
Compare that to NAT Gateway’s $0.045/GB. For a service generating 500 GB/month of traffic, the math works out to:
- Through NAT: $22.50/month (data processing only)
- Through interface endpoint: $7.30 + $5.00 = $12.30/month
That’s a 45% reduction. The savings scale linearly with traffic volume.
High-impact interface endpoints to consider:
| Service | Why It Matters |
|---|---|
| ECR (ecr.api + ecr.dkr) | Container image pulls on every deployment |
| CloudWatch Logs | Every application shipping logs |
| SQS | High-throughput message queues |
| KMS | Encryption/decryption calls |
| SSM | Systems Manager agent traffic |
| STS | Credential refresh calls |
| Secrets Manager | Secret retrieval |
Create an interface endpoint for ECR:
SUBNET_IDS=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$VPC_ID" "Name=tag:Name,Values=*private*" \
--query 'Subnets[].SubnetId' --output text | tr '\t' ',')
SG_ID=$(aws ec2 create-security-group \
--group-name ecr-endpoint-sg \
--description "Security group for ECR VPC endpoint" \
--vpc-id $VPC_ID \
--query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp --port 443 \
--cidr 10.0.0.0/8
# Create both ECR endpoints
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.ecr.api \
--subnet-ids $SUBNET_IDS \
--security-group-ids $SG_ID
aws ec2 create-vpc-endpoint \
--vpc-id $VPC_ID \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.us-east-1.ecr.dkr \
--subnet-ids $SUBNET_IDS \
--security-group-ids $SG_ID
Finding Which Services Send Traffic Through NAT
Before optimizing, you need to know where the traffic is coming from. The NAT Gateway CloudWatch metrics give you the volume, but not the destination breakdown.
Check NAT Gateway data processing volume:
NAT_GW_ID="nat-0abc123def456"
aws cloudwatch get-metric-statistics \
--namespace AWS/NATGateway \
--metric-name BytesOutToDestination \
--dimensions Name=NatGatewayId,Value=$NAT_GW_ID \
--start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 86400 \
--statistics Sum \
--query 'Datapoints[].{Date:Timestamp,Bytes:Sum}' \
--output table
Identify your NAT Gateways and their network interfaces:
aws ec2 describe-nat-gateways \
--filter "Name=state,Values=available" \
--query 'NatGateways[].{Id:NatGatewayId,AZ:SubnetId,ENI:NatGatewayAddresses[0].NetworkInterfaceId,State:State}'
The network interface ID (ENI) is what you’ll need for VPC Flow Log analysis.
VPC Flow Logs Analysis for NAT Traffic
VPC Flow Logs are the best tool for understanding exactly what traffic flows through your NAT Gateway. You can publish them to S3 and query with Athena to find the top talkers.
Enable flow logs for your VPC (publishing to S3):
aws ec2 create-flow-logs \
--resource-type VPC \
--resource-ids $VPC_ID \
--traffic-type ALL \
--log-destination-type s3 \
--log-destination arn:aws:s3:::my-flow-logs-bucket/nat-analysis/ \
--max-aggregation-interval 60
Create an Athena table for the flow logs:
CREATE EXTERNAL TABLE vpc_flow_logs (
version int,
account_id string,
interface_id string,
srcaddr string,
dstaddr string,
srcport int,
dstport int,
protocol bigint,
packets bigint,
bytes bigint,
start bigint,
`end` bigint,
action string,
log_status string
)
PARTITIONED BY (dt string)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ' '
LOCATION 's3://my-flow-logs-bucket/nat-analysis/AWSLogs/{account_id}/vpcflowlogs/{region}/';
Query top destinations by bytes through NAT:
SELECT dstaddr,
SUM(bytes) / 1073741824 AS gb_transferred,
COUNT(*) AS flow_count
FROM vpc_flow_logs
WHERE interface_id = 'eni-xxxx' -- NAT Gateway ENI
AND dt >= '2026/03/01'
GROUP BY dstaddr
ORDER BY gb_transferred DESC
LIMIT 20;
The results tell you exactly which IP addresses receive the most traffic through NAT. Cross-reference those IPs with AWS service IP ranges (available at https://ip-ranges.amazonaws.com/ip-ranges.json) to identify which AWS services are driving your NAT bill.
Check if a destination IP belongs to an AWS service:
curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | \
python3 -c "
import json, sys, ipaddress
data = json.load(sys.stdin)
target = ipaddress.ip_address('52.x.x.x') # Replace with IP from Athena
for prefix in data['prefixes']:
if target in ipaddress.ip_network(prefix['ip_prefix']):
print(f\"{prefix['service']} - {prefix['region']} - {prefix['ip_prefix']}\")
"
NAT Instances: A Cheaper Alternative for Low-Traffic Workloads
For dev environments, staging, or any workload under 100 GB/month, a NAT instance on a small EC2 instance can cut costs by 80-90%.
Cost comparison for low-traffic scenarios:
| Option | Monthly Cost (10 GB traffic) | Monthly Cost (100 GB traffic) |
|---|---|---|
| NAT Gateway | $33.30 | $37.35 |
| t4g.nano NAT instance | ~$3.07 | ~$3.07 |
| t4g.micro NAT instance | ~$6.13 | ~$6.13 |
The NAT instance doesn’t charge per GB for data processing. You pay only for the EC2 instance and standard data transfer.
Launch a NAT instance using Amazon Linux:
# Find the latest Amazon Linux 2023 AMI
AMI_ID=$(aws ec2 describe-images \
--owners amazon \
--filters "Name=name,Values=al2023-ami-2023*-arm64" \
"Name=state,Values=available" \
--query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
--output text)
# Launch in a public subnet
INSTANCE_ID=$(aws ec2 run-instances \
--image-id $AMI_ID \
--instance-type t4g.nano \
--subnet-id subnet-public-xxx \
--associate-public-ip-address \
--query 'Instances[0].InstanceId' --output text)
# Disable source/dest check (required for NAT)
aws ec2 modify-instance-attribute \
--instance-id $INSTANCE_ID \
--no-source-dest-check
# Configure iptables on the instance for NAT
# (run via SSM or SSH after launch)
# sudo sysctl -w net.ipv4.ip_forward=1
# sudo iptables -t nat -A POSTROUTING -o ens5 -j MASQUERADE
Trade-offs to consider:
- You manage patching, monitoring, and failover
- No automatic scaling (NAT Gateway scales to 100 Gbps)
- Single point of failure unless you set up an Auto Scaling Group with health checks
- Bandwidth limited by instance type (t4g.nano supports up to 5 Gbps burst)
For production workloads with high availability requirements, stick with NAT Gateway and optimize with VPC endpoints instead. For dev/test environments where occasional downtime is acceptable, NAT instances save real money.
Architecture Patterns to Reduce NAT Dependency
Beyond endpoints and instance swaps, rethinking your architecture can eliminate NAT costs at the source.
Put public-facing resources in public subnets
Load balancers, bastion hosts, and any resource that needs a public IP should be in public subnets. Only resources that genuinely need to initiate outbound connections without a public IP belong behind a NAT Gateway.
Audit resources in private subnets that might not need to be there:
# List all ENIs in private subnets with their descriptions
aws ec2 describe-network-interfaces \
--filters "Name=subnet-id,Values=subnet-private-xxx" \
--query 'NetworkInterfaces[].{ID:NetworkInterfaceId,Type:InterfaceType,Description:Description,AZ:AvailabilityZone}' \
--output table
Use S3 for data exchange instead of internet transfers
If services communicate by pulling data from external APIs, consider whether that data can be staged in S3. Internal S3 access through a gateway endpoint is free, while the same data through NAT costs $0.045/GB.
Schedule batch jobs to reduce NAT Gateway hours
If you have workloads that only need NAT access during specific windows (nightly ETL, weekly reports), you can create and delete NAT Gateways programmatically. A NAT Gateway that runs for 4 hours/day costs $5.40/month instead of $32.85.
# Create NAT Gateway for a batch window
NAT_ID=$(aws ec2 create-nat-gateway \
--subnet-id subnet-public-xxx \
--allocation-id eipalloc-xxx \
--query 'NatGateway.NatGatewayId' --output text)
# Wait for it to become available
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_ID
# ... run your batch job ...
# Delete when done
aws ec2 delete-nat-gateway --nat-gateway-id $NAT_ID
Consider AWS Regional NAT Gateway
AWS introduced a regional NAT Gateway mode in late 2025. Instead of deploying one NAT Gateway per AZ, the regional mode automatically expands and contracts across AZs based on where your workloads run. When traffic stops in an AZ, billing for that AZ stops. This eliminates paying for idle NAT Gateways in AZs with no active workloads.
Multiple NAT Gateways Per AZ: Cost Implications
The standard high-availability pattern is one NAT Gateway per AZ. For a 3-AZ deployment, that’s $98.55/month before any data processing. This adds up fast across multiple VPCs and accounts.
List all NAT Gateways and their AZs:
aws ec2 describe-nat-gateways \
--filter "Name=state,Values=available" \
--query 'NatGateways[].{Id:NatGatewayId,Subnet:SubnetId,State:State,CreateTime:CreateTime}' \
--output table
Questions to ask before deploying multi-AZ NAT:
- Does this environment actually need high availability? Dev and staging rarely do.
- Can you use a single NAT Gateway and accept brief downtime during AZ failures?
- Would the regional NAT Gateway mode handle your HA requirements more cost-effectively?
For non-production environments, a single NAT Gateway saves $32.85-$65.70/month per VPC. Across 5 dev/staging environments, that’s $164-$328/month saved with zero impact on development workflows.
CostPatrol’s NAT-O002 rule identifies idle NAT Gateways, those provisioned and billing hourly but processing little to no traffic. These are prime candidates for deletion or consolidation.
Savings Estimate Table
Here’s what typical savings look like for a team running a 3-AZ production VPC processing 500 GB/month through NAT, plus 3 dev/staging environments.
| Optimization | Current Monthly Cost | Optimized Cost | Monthly Savings |
|---|---|---|---|
| Deploy S3 gateway endpoint (300 GB S3 traffic) | $13.50 | $0.00 | $13.50 |
| Deploy DynamoDB gateway endpoint (50 GB) | $2.25 | $0.00 | $2.25 |
| ECR interface endpoint (100 GB image pulls) | $4.50 | $1.00 + $7.30 = $8.30 | -$3.80* |
| CloudWatch Logs interface endpoint (30 GB) | $1.35 | $0.30 + $7.30 = $7.60 | -$6.25* |
| Single NAT Gateway in 3 dev environments | $296 (3 x $98.55) | $98.55 (1 per env) | $197.10 |
| Replace dev NAT with NAT instances | $98.55 | $9.21 (3 x t4g.nano) | $89.34 |
| Delete idle NAT Gateways (2 unused) | $65.70 | $0.00 | $65.70 |
| Total | $357.84 |
*Interface endpoints have a fixed hourly cost that can exceed NAT data processing for low-volume services. Only deploy them for services with consistent, high-volume traffic. For low-volume services, the NAT Gateway’s per-GB charge is often cheaper than the interface endpoint’s hourly charge.
The gateway endpoints (S3 and DynamoDB) are always worth deploying. They’re free. Everything else requires doing the math for your specific traffic patterns.
Optimization Checklist
Run through this list for every VPC in your account:
- Deploy S3 gateway endpoint (free, always worth it)
- Deploy DynamoDB gateway endpoint (free, always worth it)
- Enable VPC Flow Logs and query with Athena to identify top NAT traffic destinations
- Evaluate interface endpoints for high-traffic AWS services (ECR, CloudWatch, SQS)
- Check for resources in the wrong AZ incurring cross-AZ transfer through NAT
- Delete NAT Gateways in environments that don’t need internet access
- Switch to single NAT Gateway in non-production environments
- Consider NAT instances for dev/staging with less than 100 GB/month traffic
- Review whether regional NAT Gateway mode fits your availability pattern
- Set up CloudWatch alarms on BytesOutToDestination to catch traffic spikes early
- Audit private subnet resources. Do they all actually need NAT?
Set up a CloudWatch alarm for unexpected NAT traffic spikes:
aws cloudwatch put-metric-alarm \
--alarm-name "NAT-Gateway-High-Data-Processing" \
--metric-name BytesOutToDestination \
--namespace AWS/NATGateway \
--dimensions Name=NatGatewayId,Value=nat-0abc123def456 \
--statistic Sum \
--period 86400 \
--threshold 10737418240 \
--comparison-operator GreaterThanThreshold \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:123456789:billing-alerts
That alarm triggers when daily NAT traffic exceeds 10 GB. Adjust the threshold based on your baseline.
CostPatrol scans for both high data processing (NAT-O001) and idle NAT Gateways (NAT-O002) as part of its optimization checks. If you’re managing multiple accounts or VPCs, automated detection catches the issues that manual audits miss.