Skip to main content

The $32/month surprise: finding and deleting idle NAT Gateways

· 4 min read
Founder, Dragon Fractal · ex-AWS engineer

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-gateways
  • aws ec2 delete-nat-gateway