AWS bills rarely jump because of the thing you were watching. They jump because of a component that was doing its job quietly, in a line item named something other than the service you think you are running.
The NAT gateway is the most common one. It is also the one where the arithmetic surprises people, because it charges in two directions at once.
The charge nobody budgets for
A NAT gateway exists so instances in a private subnet can make outbound connections — install packages, pull container images, call third-party APIs — without being reachable from the internet. Sensible architecture, and most reference diagrams include one.
The billing has three layers:
- An hourly charge per gateway, which accrues whether or not any traffic passes through it.
- A per-gigabyte processing charge on everything that does.
- Internet egress, billed separately on top, when that traffic leaves AWS.
Published rates for us-east-1 put the first two at roughly $0.045 per hour and $0.045 per GB processed, with internet egress around $0.09 per GB at first-tier rates. That means a single gigabyte can be billed at about $0.135 on its way out — processing plus egress — and the gateway costs roughly $33 a month before carrying a single byte.
Those figures are from a vendor blog and change by region and over time, so verify against AWS's own pricing pages before budgeting. The structure is the point: a per-hour charge you cannot avoid, plus a per-GB charge that scales with traffic you may not realise is flowing.
The traffic you did not know you had
Once workloads sit in private subnets, a surprising amount routes through NAT by default:
- Package installs on every deploy and every container build
- Container image pulls, which are large and frequent
- Calls to AWS services that have a public endpoint — including S3
- Telemetry, log shipping and metric agents, continuously
That S3 line is the expensive one. S3 is reached over its public endpoint by default, so a workload reading or writing objects from a private subnet sends all of it through the NAT gateway and pays processing on every gigabyte — to reach a service sitting inside AWS.
One engineering team documented what that looks like when it goes wrong: an S3 sync routed through NAT by mistake, 20,167 GB of NAT data transfer in a single day, billed at $907.53. Cost Anomaly Detection caught it within days, and the fix was a single missing VPC endpoint. AWS refunded that particular bill after an explanation — treat that as one team's experience rather than a policy you can rely on.
What makes this failure mode nasty is that nothing is broken. The application works. The architecture matches the diagram. The only symptom is a number on an invoice at the end of the month.
Finding it
Start in Cost Explorer, and look in the right place — NAT charges do not appear under a service called "NAT":
- Filter Service to
EC2 - Other. - Group by Usage type.
- Look for
NatGateway-Bytes(processing) andDataTransfer-Out(egress).
If processing volume looks implausible for your traffic, enable VPC flow logs on the subnets and aggregate by destination. That tells you which workload and which endpoint are responsible, which is the information you actually need — "NAT is expensive" is not actionable, "the nightly export is pushing 600 GB to S3 through NAT" is.
The fixes, in order of return
Gateway endpoints for S3 and DynamoDB. These route traffic to those two services inside the AWS network, bypassing NAT entirely, and they carry no hourly or per-GB charge. For most workloads this is the single biggest reduction available, and it is a route table entry plus an endpoint:
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = [aws_route_table.private.id]
}Interface endpoints for other services. ECR, Secrets Manager, CloudWatch Logs and others support PrivateLink. These do have an hourly and per-GB cost, so they are not free — but the per-GB rate is lower than NAT processing, and for high-volume services like container image pulls they pay back quickly. Add them where the volume justifies it, not reflexively.
Fewer gateways. A NAT gateway per availability zone is the resilient pattern and multiplies the hourly charge. In non-production environments, one is usually enough.
Release idle public IPv4 addresses. Public IPv4 is now billed at roughly $0.005 per hour whether attached or idle — about $44 a year each. Forgotten addresses from old experiments are pure waste.
Ask whether you need NAT at all. It exists to give private subnets outbound access. A small application in a public subnet with tight security groups, or one whose outbound needs are entirely covered by VPC endpoints, may not need one. Default to the private-subnet pattern when the workload warrants it — not because the diagram had it.
Prevention beats diagnosis
Three things, each about ten minutes:
- Cost Anomaly Detection, configured before you need it. It is what caught the incident above within days rather than at the end of the month.
- A budget alert at a threshold you would genuinely want to be woken for.
- A monthly five-minute read of Cost Explorer grouped by usage type. Not a review meeting — a scan for line items that are larger than your mental model of the system.
Payload size is the other half of this. Traffic you do not send is traffic you are not billed for, which is why compressing a cache from 14 GiB to 1.6 GiB pays twice — in memory and in transfer.
The takeaway
The NAT gateway is not expensive because AWS priced it badly. It is expensive because it sits in the default path of traffic that has no business going through it — chiefly S3 — and the billing compounds hourly and per gigabyte at the same time.
Add gateway endpoints for S3 and DynamoDB, confirm in Cost Explorer that NatGateway-Bytes dropped, and turn on anomaly detection so the next surprise arrives as an alert rather than an invoice.