The NAT gateway tax: the line item nobody reads
The Lizrd team · · 3 min read
Ask an engineer what their biggest cloud cost is and they’ll say compute. Pull the bill and you’ll often find a surprise sitting in the top three: NAT gateway charges. Not the hourly rate — that’s trivial — but the per-GB data processing fee, multiplied by every byte your private subnets send to the internet, S3, or another AZ.
It’s the classic invisible cost. Nobody provisions a NAT gateway thinking about throughput, and the bill never says which workload pushed the traffic. So it grows, unattributed, until someone finally reads the line.
Where the money actually goes
There are two charges stacked on top of each other, and teams routinely conflate them:
- NAT data processing — a per-GB fee on everything that transits the gateway, regardless of destination.
- Cross-AZ data transfer — a separate per-GB fee when traffic crosses availability zones, which a poorly placed NAT gateway triggers constantly.
The worst case is a single NAT gateway in one AZ serving workloads in three. Every packet from the other two zones pays cross-AZ transfer to reach the gateway, then data processing through it. You’re billed twice for a routing accident.
The fix starts with visibility
You cannot optimize traffic you cannot see. Turn on VPC Flow Logs, then answer three questions:
- What’s the destination? A huge share of NAT traffic is workloads talking to AWS services — S3, ECR, DynamoDB, CloudWatch — that never needed the public internet at all.
- Is it crossing AZs? Group bytes by source and gateway AZ.
- Who owns it? Map the top talkers back to a service, not just an ENI.
That third question is the one that stays unanswered in most orgs, and it’s the one that turns a number into an action.
The highest-leverage change
For AWS-service traffic, a VPC endpoint takes the gateway out of the path entirely:
+ resource "aws_vpc_endpoint" "s3" {
+ vpc_id = aws_vpc.main.id
+ service_name = "com.amazonaws.${var.region}.s3"
+ # Gateway endpoint — no NAT data-processing fee on S3 traffic
+ }
Gateway endpoints for S3 and DynamoDB are free and remove that traffic from the NAT bill outright. Interface endpoints for other services carry an hourly cost, so rank them by the GBs they’d divert — the busiest few usually pay for themselves in days.
Then place one NAT gateway per AZ so no workload pays to cross a zone just to reach the internet. It’s more gateways, but the cross-AZ transfer you eliminate almost always dwarfs the extra hourly rate.
Make it continuous, not a one-off audit
Traffic patterns drift. A new service starts chatting to an external API, a log shipper doubles its volume, and the tax creeps back. The teams that stay lean treat egress like any other utilization signal — watched continuously and attributed to an owner.
That’s exactly the visibility we built Lizrd for: it reads your flows and billing together, surfaces which workloads are driving NAT and transfer spend, and proposes the endpoint or routing change as a concrete diff — read-only, with the savings proven back once it lands. The NAT tax is only invisible until someone shines a light on it.