You opened Cost Explorer, grouped by usage type, and there it is: a four-figure line item for something called NatGateway-Bytes or NatGateway-Hours. You didn't change anything. Nobody spun up a new service. And yet a piece of networking plumbing you set up once and forgot about is now one of the biggest lines on your AWS bill. This post explains exactly what you're paying for, how to find the traffic that's driving it, and how to cut it — usually without breaking anything.
What am I actually paying for?
A NAT Gateway has two completely separate charges, and confusing them is why the bill feels so mysterious.
The first is an hourly charge just for existing: roughly $0.045 per hour in most US regions. That's about ~$33 per month per gateway, billed whether or not a single byte flows through it. You pay it per Availability Zone — so a "highly available" setup with one NAT Gateway in each of three AZs costs you ~$99/month before any traffic at all.
The second is a data-processing charge: roughly $0.045 per GB processed, on top of the normal data-transfer fees AWS already charges. This is the one that shocks people. Every gigabyte your private subnets send out through the gateway is metered again, purely for the privilege of passing through NAT.
The figures here are typical US-region on-demand rates at the time of writing and are rounded for clarity. AWS changes prices and rates vary by region, so treat these as ballpark — always confirm against the AWS NAT Gateway pricing page for your region.
If your bill is dominated by NatGateway-Hours, you probably just have more gateways than you need. If it's dominated by NatGateway-Bytes, you have a traffic problem — and that's almost always the expensive one.
Why is the data-processing charge usually the culprit?
Here's the part that catches everyone: you are paying per gigabyte to reach services that live inside AWS.
When an EC2 instance, container, or Lambda in a private subnet talks to S3, DynamoDB, ECR, CloudWatch, or almost any other AWS service, the default path is out through the NAT Gateway. From the gateway's perspective that traffic is "internet-bound," so it gets metered at ~$0.045/GB — even though the destination never actually leaves the AWS network.
Think about what that means for a real workload:
- A data pipeline reading 2 TB/day from S3 through NAT costs roughly 2,000 GB × $0.045 = ~$90/day, or ~$2,700/month in NAT data processing alone.
- A fleet of containers pulling large images from ECR on every deploy or scale-out event racks up per-GB charges every single pull.
- Verbose application logs shipping to CloudWatch all day, every day, quietly metered by the gigabyte.
None of that traffic needs to touch a NAT Gateway. It's reaching AWS services that sit right next to your VPC. You're paying a toll to cross a bridge to a building on the same street.
How do I find out where the traffic is going?
Before you change anything, confirm which gateway is expensive and what's flowing through it.
Start with the CloudWatch metrics the gateway publishes. The BytesOutToDestination metric tells you how much data the gateway sent toward external destinations — the number your data-processing charge is based on. Pull the last two weeks for a specific gateway:
aws cloudwatch get-metric-statistics \
--namespace AWS/NATGateway \
--metric-name BytesOutToDestination \
--dimensions Name=NatGatewayId,Value=nat-0123456789abcdef0 \
--start-time "$(date -u -d '14 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 86400 \
--statistics Sum \
--region us-east-1 \
--output table
A high, steady Sum confirms the gateway is doing real work. A flat line near zero means you're paying the hourly fee for nothing (more on that below).
To find out what that traffic is, turn on VPC Flow Logs for the subnets behind the gateway. Flow logs record source, destination, and byte counts per flow, so you can see whether the top destinations are S3 prefixes, DynamoDB, ECR, or genuinely external endpoints. If the biggest talkers are AWS service IP ranges, you've found your fix.
A quick way to read them once they're flowing: send flow logs to CloudWatch Logs or S3, then use CloudWatch Logs Insights (or Athena over the S3 logs) to sum bytes grouped by dstaddr. Cross-reference the top destination IPs against the AWS IP ranges list — if the heaviest destinations map to S3, DYNAMODB, or another service's range, that traffic never needed to leave AWS and every gigabyte of it was billed by your NAT Gateway for nothing.
Should I use a gateway endpoint or an interface endpoint?
The fix for AWS-bound traffic is a VPC endpoint — a private door from your VPC directly to the service, bypassing the NAT Gateway entirely. But there are two kinds, and the difference matters to your bill.
Gateway endpoints cover S3 and DynamoDB, and they are free. No hourly charge, no per-GB charge. You add them to your route tables and the relevant traffic stops flowing through NAT immediately. For most teams, S3 and DynamoDB gateway endpoints remove the majority of the data-processing charge for zero ongoing cost. This is the single highest-leverage change you can make.
Interface endpoints (AWS PrivateLink) cover everything else — ECR, CloudWatch, Secrets Manager, SQS, and dozens more. These are not free: they bill an hourly charge per endpoint per AZ (roughly $0.01/hour, ~$7/month per AZ) plus a small per-GB fee. They're still usually cheaper than routing heavy traffic through NAT, but there's a catch.
Because interface endpoints charge by the hour whether you use them or not, one you created "just in case" and never routed traffic to is pure waste — the same trap as an over-provisioned NAT Gateway, just smaller. This is exactly why Parsivex has a separate Idle VPC interface endpoint detector. Add interface endpoints for services you actually talk to in volume, not speculatively.
The honest rule of thumb: add gateway endpoints for S3 and DynamoDB everywhere (they're free, so there's no downside), and add interface endpoints only where the traffic justifies the hourly cost. Then go back and delete any interface endpoints that aren't earning their keep.
When can I just delete the NAT Gateway?
Sometimes the cheapest gateway is no gateway. If a NAT Gateway's BytesOutToDestination is essentially flat zero for 14+ days, nothing in that subnet is using it — you're paying ~$33/month per AZ for a gateway that isn't moving data.
Deleting a NAT Gateway removes outbound internet access for everything in the subnets that route through it. Before you delete, check the route tables that point at it and confirm no instance, container, or Lambda still needs to reach the public internet (for third-party APIs, OS package updates, etc.). If in doubt, remove the route first and watch for breakage before deleting the gateway itself.
The most common way this happens: a "one NAT Gateway per AZ" template deployed for high availability into AZs that never actually received workloads. You end up with two or three gateways when one — or zero — would do. Each idle one is ~$33/month you can hand straight back.
How do I find this across my whole AWS account?
Doing this by hand for one gateway is straightforward. Doing it for every gateway, every subnet, and every unused interface endpoint across every region is where it gets tedious — and where the waste hides. That's what a scan is for. Parsivex checks each region, flags NAT Gateways whose data-processing charges could be cut with endpoints, spots interface endpoints you're paying for but not using, and estimates the monthly savings for each.
For what these findings mean once they show up in your report, see NAT Gateway overuse and Idle VPC interface endpoint, or read how scans work before you connect an account.