The $32/month surprise: finding and deleting idle NAT Gateways
Short version: Every NAT Gateway costs $0.045/hour, about $32/month, before a single byte of data, plus $0.045 per GB processed. They are easy to create per subnet and easy to forget. Here is how to find NAT Gateways that carry little or no traffic, delete them without breaking egress, and stop paying NAT data charges for traffic that could use free VPC endpoints instead.
Why NAT Gateways get expensive quietly
A NAT Gateway has two costs (us-east-1):
- Hourly: $0.045/hour ≈ $32/month, flat, whether it moves 0 bytes or 0 GB.
- Data processing: $0.045 per GB that passes through it.
The trap is the hourly charge. A common HA pattern puts one NAT Gateway in each Availability Zone - three AZs is ~$96/month in fixed cost. Leftovers from deleted environments, or NATs in subnets that no longer send traffic, bill the full $32/month indefinitely.
Step 1 - List your NAT Gateways
aws ec2 describe-nat-gateways \
--filter Name=state,Values=available \
--query 'NatGateways[].{ID:NatGatewayId,VPC:VpcId,Subnet:SubnetId}' \
--output table
Across all regions:
for region in $(aws ec2 describe-regions --query 'Regions[].RegionName' --output text); do
echo "== $region =="
aws ec2 describe-nat-gateways --region "$region" \
--filter Name=state,Values=available \
--query 'NatGateways[].NatGatewayId' --output text
done
Step 2 - Check whether each one is actually used
Idle means little to no traffic. Pull the last 7 days of bytes from CloudWatch for a given NAT Gateway:
aws cloudwatch get-metric-statistics \
--namespace AWS/NATGateway \
--metric-name BytesOutToDestination \
--dimensions Name=NatGatewayId,Value=nat-0abc123def456 \
--start-time "$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 86400 --statistics Sum \
--query 'Datapoints[].Sum'
Near-zero sums over a week means nothing is using it. Also confirm no route table
still points 0.0.0.0/0 at it:
aws ec2 describe-route-tables \
--query "RouteTables[?Routes[?NatGatewayId=='nat-0abc123def456']].RouteTableId" \
--output text
Step 3 - Delete it safely
If no route table depends on it and traffic is flat, delete it:
aws ec2 delete-nat-gateway --nat-gateway-id nat-0abc123def456
Then release the Elastic IP it held (that is billable too - see the unattached Elastic IP post).
Caveat: a NAT Gateway provides outbound internet for private subnets. If a workload in that subnet needs egress (package installs, external APIs, updates), deleting the NAT breaks it. Verify traffic is genuinely flat and no active route depends on it before deleting. In multi-AZ setups, deleting one AZ's NAT sends that AZ's traffic cross-AZ (added data cost) or breaks it - decide deliberately.
Cut NAT data charges for free
Traffic to S3 and DynamoDB does not need a NAT Gateway at all. A VPC gateway endpoint routes it privately, for free, and removes those bytes from your NAT data-processing bill:
aws ec2 create-vpc-endpoint \
--vpc-id vpc-0abc123 \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-0abc123
Do it automatically
Checking NAT traffic across every VPC, subnet, and region by hand does not scale.
Cloud Cost Analyzer's idle-nat-gateway rule flags any NAT Gateway moving less than
1 GB/day over a 7-day window, alongside 89 other cost rules:
curl -sSL https://releases.dragonfractal.com/install.sh | sh
cca scan --provider aws
The agent runs in your environment with read-only access, so your AWS credentials never leave it. See the AWS setup and required IAM permissions
CLI Reference
aws ec2 describe-nat-gatewaysaws ec2 delete-nat-gateway