NAT Gateway Costs and AWS Data Transfer, Explained
Cut NAT gateway costs and AWS data transfer charges: per-GB processing, cross-AZ traffic, S3 gateway endpoints, internet egress and how to find them.
Nobody provisions a NAT gateway thinking it’ll be expensive. You pick “1 per AZ” in the VPC wizard, or accept whatever your CDK or Terraform module does by default, the private subnets can reach the internet, and everyone moves on. A year later someone asks why “EC2 - Other” costs half as much as the instances themselves.
The answer is almost always bytes. NAT gateways charge by the hour, but they also charge for every gigabyte they process, and that second charge has no ceiling. Add cross-AZ transfer and internet egress on top, and you have a set of costs that grow with traffic, live under vague service names, and rarely get an owner.
Here’s how the charges stack up, the fixes that work, and the ones that sound good but don’t. Prices are on-demand list prices in us-east-1 at the time of writing, from the AWS price list.
What you’re actually paying for
| Charge | us-east-1 list price | Usage type in Cost Explorer |
|---|---|---|
| NAT gateway, per hour | $0.045 | NatGateway-Hours |
| NAT gateway, data processed | $0.045 per GB | NatGateway-Bytes |
| Cross-AZ transfer | $0.01 per GB, each direction | DataTransfer-Regional-Bytes |
| Internet egress, first 10 TB/month | $0.09 per GB | DataTransfer-Out-Bytes |
| Public IPv4 address | $0.005 per hour | PublicIPv4:InUseAddress |
| Interface VPC endpoint | $0.01 per hour per AZ, plus $0.01 per GB | VpcEndpoint-Hours, VpcEndpoint-Bytes |
| Gateway VPC endpoint (S3, DynamoDB) | Free | none |
Outside us-east-1, these usage types carry a region prefix, such as USW2-NatGateway-Bytes. The public IPv4 and endpoint ones usually have a prefix even in us-east-1.
A NAT gateway left running costs about $32.85 a month before it moves a single byte (730 hours at $0.045). One per AZ across three AZs is about $98.55 a month of fixed cost. That’s fine for production. It’s a lot for a sandbox VPC that hasn’t had a running instance since spring.
The charges stack
The thing people miss is that these charges add up on the same byte. A gigabyte going from a private instance, through a NAT gateway in another AZ, out to the internet pays three times:
- $0.01 to cross the AZ boundary to the NAT gateway (the crossing is billed on the instance side)
- $0.045 for NAT processing
- $0.09 for internet egress
That’s $0.145 per GB. For a service pushing 5 TB a month to a third-party API, it comes to roughly $740 a month in network charges alone.
Internet egress gets cheaper with volume: $0.085 per GB for the next 40 TB, $0.07 for the next 100 TB and $0.05 beyond 150 TB. The first 100 GB a month is free, aggregated across services and regions. NAT processing doesn’t get a volume discount.
Fix 1: Gateway endpoints for S3 and DynamoDB
If you do one thing after reading this, do this. Traffic from EC2 to S3 in the same region has no data transfer charge, but if it goes through a NAT gateway, you still pay $0.045 per GB to process it. Ten terabytes of backups, logs or data lake reads a month is about $460 in NAT processing for traffic that could have been free.
A gateway endpoint for S3 (and one for DynamoDB) adds a route to your route tables that sends that traffic straight to the service. There’s no hourly charge and no per-GB charge.
Check whether you already have them:
# Read-only: S3 and DynamoDB gateway endpoints in this region
aws ec2 describe-vpc-endpoints \
--filters Name=vpc-endpoint-type,Values=Gateway \
--query "VpcEndpoints[].[VpcId,ServiceName,State,length(RouteTableIds)]" \
--output table
A few things to know before you add one:
- It only works for buckets and tables in the same region. Cross-region S3 traffic still goes through the NAT.
- It doesn’t extend to on-premises networks, peered VPCs or traffic coming through a transit gateway.
- Associating route tables changes routes, which can drop open TCP connections. Do it in a quiet window.
- Bucket policies that use
aws:SourceIpwill stop matching, since traffic now arrives from private addresses. Useaws:VpcSourceIporaws:SourceVpceinstead.
Fix 2: Interface endpoints, but only where the maths works
Other AWS services, such as ECR, CloudWatch Logs, STS, Secrets Manager and SQS, can be reached through interface endpoints (PrivateLink). They aren’t free. In us-east-1, an endpoint costs $0.01 per hour in each AZ, plus $0.01 per GB processed.
An interface endpoint in three AZs runs about $21.90 a month before data. It saves the difference between NAT processing and endpoint processing, $0.035 per GB. So the endpoint pays for itself at roughly 626 GB a month through that service. ECR image pulls on a busy EKS cluster clear that easily. An endpoint for KMS in a VPC that makes a few thousand small calls a day doesn’t.
Two traps. First, teams sometimes create a dozen interface endpoints in every VPC “for security”, then realise each one is a fixed monthly charge per AZ. Second, adding endpoints doesn’t remove the NAT gateway’s hourly charge. You save on bytes, not on the gateway itself, unless the endpoints cover everything that needed the NAT.
Fix 3: Stop paying to cross AZs
Cross-AZ transfer is $0.01 per GB in each direction. When both ends are your own instances, both sides are billed, so a byte that crosses costs $0.02 in total. Traffic to a NAT gateway in another zone is billed once, on the instance side, which is the $0.01 in the example above. The common causes:
- One NAT gateway for a multi-AZ VPC. Instances in the other AZs send every outbound byte across a zone boundary to reach it. For dev accounts with low traffic, a single NAT is often the right trade. For busy production, one per AZ with matching route tables usually wins.
- Chatty services spread across zones. Microservices calling each other across AZs, Kafka replication, and databases with replicas in other zones all generate
DataTransfer-Regional-Bytes. Some of this is the price of high availability. Some is just an accident of scheduling. - Network Load Balancers with cross-zone load balancing turned on, which send requests to targets in other AZs and pay regional transfer for it.
You shouldn’t collapse everything into one AZ to save transfer. That’s trading a predictable bill for an outage. But it’s worth knowing which flows are crossing and whether they need to.
Regional NAT gateways
Since November 2025, NAT gateways have a regional availability mode, available in all commercial regions. Instead of one zonal gateway per AZ, you create one regional gateway for the VPC. It expands into the AZs where you have workloads and contracts when they’re gone, and it doesn’t need public subnets.
On cost, don’t expect a discount. The VPC pricing page bills a regional NAT gateway per AZ it’s active in, at the same hourly rate, and data processing is still charged per GB. What it does fix is the operational drift that causes cross-AZ waste: a new AZ gets NAT coverage without someone remembering to add a gateway and update route tables. Expanding into a new AZ can take up to 60 minutes, and until it finishes, traffic from that AZ is processed in one of the zones the gateway already covers. Regional NAT gateways don’t support private NAT, so private NAT gateways stay zonal.
Fix 4: IPv6 and the egress-only gateway
Every public IPv4 address now costs $0.005 an hour, about $3.65 a month each, including the Elastic IPs on your NAT gateways. That’s small per address, but accounts with hundreds of public IPs notice.
For IPv6 traffic, you don’t need a NAT gateway at all. An egress-only internet gateway lets private IPv6 instances make outbound connections while blocking inbound ones, and the gateway itself is free. You still pay normal data transfer out, but there’s no per-GB processing charge. If the APIs and registries your workloads call support IPv6, dual-stack subnets move that traffic off the NAT. Getting there takes real work across subnets, security groups and DNS, so start with the highest-volume destinations.
Fix 5: Put CloudFront in front of internet egress
Data transfer from an AWS origin such as S3, an ALB or EC2 to CloudFront is free. CloudFront’s own egress to viewers in the US starts at $0.085 per GB for the first 10 TB, slightly below the $0.09 for direct egress, with steeper volume tiers. Caching also means many requests never reach your origin at all.
For public downloads, media and static assets, this is usually a clear win. For APIs with uncacheable responses, the per-GB saving is modest and you add request charges, so check the numbers. CloudFront also has flat-rate plans now, which we covered in AWS billing discounts you probably missed.
How to find these charges
In Cost Explorer
NAT gateway charges show up under the service EC2 - Other, not under VPC. Filter to that service and group by Usage type to see NatGateway-Hours and NatGateway-Bytes separately. Then remove the service filter and group by usage type with a search for DataTransfer to see regional and outbound transfer.
The same thing from the CLI for last calendar month (read-only, though each Cost Explorer API request is billed at $0.01). The first date form is GNU, the fallback after || is for macOS:
START=$(date -u -d "$(date -u +%Y-%m-01) -1 month" +%Y-%m-%d 2>/dev/null || date -u -v1d -v-1m +%Y-%m-%d)
END=$(date -u +%Y-%m-01)
aws ce get-cost-and-usage \
--time-period Start="$START",End="$END" \
--granularity MONTHLY \
--metrics UnblendedCost UsageQuantity \
--filter '{"Dimensions":{"Key":"SERVICE","Values":["EC2 - Other"]}}' \
--group-by Type=DIMENSION,Key=USAGE_TYPE
Per gateway, in CloudWatch
Cost Explorer tells you how much. CloudWatch tells you which gateway:
# Read-only: list NAT gateways, then bytes sent out by one of them over 7 days
aws ec2 describe-nat-gateways \
--query "NatGateways[].[NatGatewayId,VpcId,SubnetId,State]" --output table
START=$(date -u -d '7 days ago' +%Y-%m-%dT00:00:00Z 2>/dev/null || date -u -v-7d +%Y-%m-%dT00:00:00Z)
END=$(date -u +%Y-%m-%dT00:00:00Z)
aws cloudwatch get-metric-statistics \
--namespace AWS/NATGateway --metric-name BytesOutToDestination \
--dimensions Name=NatGatewayId,Value=nat-0123456789abcdef0 \
--start-time "$START" --end-time "$END" \
--period 86400 --statistics Sum
A gateway with near-zero bytes for weeks is a deletion candidate.
Who’s sending the bytes: VPC Flow Logs
To find out which instances and destinations drive the volume, turn on VPC Flow Logs for the NAT gateway’s network interface with a custom format. The useful fields are pkt-srcaddr (the private instance behind the NAT), pkt-dstaddr, pkt-dst-aws-service (labels destinations like S3 or DYNAMODB), flow-direction and bytes. Creating a flow log isn’t destructive, but it does add log delivery and storage charges, so send it to S3 rather than CloudWatch Logs (the CloudWatch Logs to S3 post has the price gap) and turn it off when you’re done.
Query the logs in Athena and sum bytes grouped by pkt_dst_aws_service and pkt_srcaddr. If S3 or DynamoDB sits at the top, that’s Fix 1. If one instance dominates, go and ask its owner what it’s downloading. We once traced a large share of a NAT bill to a container that pulled the same multi-gigabyte model file at every restart.
FAQ
Why is my NAT gateway so expensive?
Usually it’s the per-GB processing charge, not the hourly one. Look at NatGateway-Bytes in Cost Explorer. If it’s large, the most common cause is S3 or DynamoDB traffic that should be going through a free gateway endpoint.
Does traffic to S3 through a NAT gateway cost money?
Yes. Same-region transfer between EC2 and S3 is free, but the NAT gateway still charges $0.045 per GB in us-east-1 to process it. An S3 gateway endpoint removes that charge.
How much does cross-AZ data transfer cost on AWS?
In us-east-1, $0.01 per GB in each direction. Between two of your own instances that’s $0.02 for each gigabyte that crosses, since both sides are billed. It appears as DataTransfer-Regional-Bytes.
Is a regional NAT gateway cheaper than zonal NAT gateways?
Not on list price. It’s billed per active AZ at the same hourly rate, with the same per-GB processing. It can reduce cross-AZ waste by covering every workload AZ automatically.
Should I replace NAT gateways with interface endpoints?
Only for services with enough traffic. In three AZs, an interface endpoint pays for itself at roughly 626 GB a month versus NAT processing. Below that, the NAT gateway is cheaper.
Two checks, ten minutes
In Cost Explorer, filter to EC2 - Other, group by usage type and read last month’s NatGateway-Bytes. Then open the route tables of every VPC that has a NAT gateway and look for a route whose target is an S3 gateway endpoint. In most accounts we see, that pair of checks finds the biggest single leak, and fixing it costs nothing. Kostrix’s scan looks for the same two things: VPCs that send S3 or DynamoDB traffic through a NAT gateway instead of a gateway endpoint, and NAT gateways that bill by the hour while moving next to no traffic.
