About this article: AI-assisted writing, reviewed against the cited sources. This is source research, not a lab validation of the procedures. Check the stated versions and your own recovery requirements before making changes.
A NAT gateway bill has separate parts: an hourly charge, a per-GB processing charge, a charge for each public IPv4 address, and whatever data transfer the same traffic would attract anyway. Trace it in three passes. The bill tells you which charge grew. CloudWatch tells you when. Flow logs on the gateway's network interface tell you which client talked to which destination.
The commands and queries below were checked against AWS documentation. None was run in a lab. Those marked "written for this article" were assembled from documented parts.
Why it matters
Processing is charged on the traffic, not on what the traffic was for. AWS charges per GB processed "regardless of the traffic's source or destination", and says you also incur standard data transfer charges for data sent through the gateway.
So the same byte can be charged more than once, and a fix aimed at the wrong part saves nothing. Moving a backup job that writes to S3 or DynamoDB in the same Region onto a gateway endpoint removes processing charges for that traffic. Adding a NAT gateway per Availability Zone (AZ) does not reduce processing at all.
How the charges fit together
The Sydney rates below come from the AWS Price List offers for ap-southeast-2: the EC2 offer published 2026-11-07, the data transfer offer published 2026-11-03, and the VPC offer published 2026-11-05. They are public on-demand US dollar rates. Check them again before relying on them.
| Usage type | What it charges | Sydney rate |
|---|---|---|
APS2-NatGateway-Hours |
Each hour a zonal gateway is provisioned and available | US$0.059 per hour |
APS2-NatGateway-Bytes |
Data processed by a zonal gateway | US$0.059 per GB |
APS2-RegionalNatGateway-Hours |
Regional gateway hours | US$0.059 per hour |
APS2-RegionalNatGateway-Bytes |
Data processed by a regional gateway | US$0.059 per GB |
APS2-PublicIPv4:InUseAddress |
Each in-use public IPv4 address, including Elastic IPs attached to a NAT gateway | US$0.005 per hour |
APS2-DataTransfer-Regional-Bytes |
Data in, out or between EC2 AZs, or using Elastic IPs or load balancers | US$0.010 per GB |
APS2-DataTransfer-Out-Bytes |
Internet egress, first 10 TB tier | US$0.114 per GB |
APS2-USE1-AWS-Out-Bytes |
Example inter-Region path, Sydney to N. Virginia | US$0.098 per GB |
APS2-VpcEndpoint-Hours |
Interface endpoint hours | US$0.013 per hour |
APS2-VpcEndpoint-Bytes |
Interface endpoint processing, first 1 PB | US$0.010 per GB |
A few pricing rules decide how these combine.
A partial NAT gateway hour bills as a full hour. A regional NAT gateway is charged per hour for each AZ it covers, so AWS's own example has three AZs costing three times the hourly rate.
In AWS's worked example, an EC2 instance sends 1 GB to S3 in the same Region through a NAT gateway in its own AZ. It pays processing and no data transfer. The same example says there are data transfer charges when the instance and gateway sit in different AZs, and that traffic to a non-AWS internet address pays data transfer out.
The first 100 GB out to the internet each month is free, aggregated across services and Regions, excluding China and GovCloud. The volume tiers also aggregate across services, so a NAT gateway's traffic may fall in a cheaper tier than its own volume would reach.
Cross-AZ traffic is billed on both sides. The Cost and Usage Report guide says inter-AZ transfer is "charged for both inbound and outbound", and shows two line items per transfer. In practice a cross-AZ GB costs US$0.02 in Sydney if both sides are yours.
A note on downloads. AWS's "regardless of source or destination" wording covers them, by this article's reading, but the pricing page does not state a download example. Step 2 below gives a way to confirm the reading against your own bill.
Step 1: find which charge moved
Start with the bill, grouped by usage type. This form was written for this article from the get-cost-and-usage reference.
cat > nat-filter.json <<'EOF'
{
"Dimensions": {
"Key": "USAGE_TYPE",
"Values": [
"APS2-NatGateway-Hours",
"APS2-NatGateway-Bytes",
"APS2-RegionalNatGateway-Hours",
"APS2-RegionalNatGateway-Bytes",
"APS2-PublicIPv4:InUseAddress",
"APS2-DataTransfer-Regional-Bytes",
"APS2-DataTransfer-Out-Bytes"
]
}
}
EOF
aws ce get-cost-and-usage \
--time-period Start=2026-10-01,End=2026-11-01 \
--granularity DAILY \
--metrics UnblendedCost UsageQuantity \
--group-by Type=DIMENSION,Key=USAGE_TYPE \
--filter file://nat-filter.json
The end date is exclusive. Replace APS2 for other Regions. Usage types without a Region prefix belong to N. Virginia.
In the Cost Explorer console, the usage type groups "EC2: NAT Gateway - Data Processed", "EC2: NAT Gateway - Running Hours", "EC2: Data Transfer - Inter AZ" and "EC2: Data Transfer - Internet (Out)" give the same split.
If a Cost and Usage Report is delivered to Athena, the line item columns allow a daily view. Written for this article, with a placeholder table name:
SELECT date(line_item_usage_start_date) AS day,
line_item_usage_type,
SUM(line_item_usage_amount) AS usage,
SUM(line_item_unblended_cost) AS cost
FROM cur_table
WHERE line_item_usage_type LIKE '%NatGateway%'
OR line_item_usage_type LIKE '%DataTransfer-Regional-Bytes'
OR line_item_usage_type LIKE '%PublicIPv4:InUseAddress'
GROUP BY 1, 2
ORDER BY 1, 2;
Read the output this way:
| What grew | What it points to |
|---|---|
NatGateway-Hours |
More gateways, or more AZs on a regional gateway |
NatGateway-Bytes alone |
Either traffic to AWS services in the same Region, or downloads from the internet, because data coming in from the internet has no transfer charge |
PublicIPv4:InUseAddress |
More public addresses. This covers the whole account, not only NAT gateways |
NatGateway-Bytes with DataTransfer-Out-Bytes |
Traffic to the internet |
NatGateway-Bytes with DataTransfer-Regional-Bytes |
Clients may be crossing AZs to reach the gateway, though other cross-AZ traffic shares that usage type |
These are readings, not proof. Only the flow logs in step 3 tie a charge to a client.
The line_item_resource_id column appears only when the report was set up with resource IDs, and AWS says it is blank for usage types not tied to an instantiated host, such as data transfers. Do not expect it to attribute data transfer lines.
Step 2: find when, and check the volume
NAT gateway metrics are published to CloudWatch at one-minute intervals and kept for 15 months, in the AWS/NATGateway namespace. Four byte metrics matter here:
| Metric | Meaning | Use |
|---|---|---|
BytesInFromSource |
Received from clients in your VPC | Uploads |
BytesOutToDestination |
Sent on towards the destination | Uploads leaving |
BytesInFromDestination |
Received from the destination | Downloads |
BytesOutToSource |
Sent back to clients | Downloads delivered |
AWS's traffic source guidance uses BytesInFromSource for uploads and BytesInFromDestination for downloads.
aws cloudwatch get-metric-statistics \
--namespace AWS/NATGateway \
--metric-name BytesInFromDestination \
--dimensions Name=NatGatewayId,Value=nat-0123456789abcdef0 \
--start-time 2026-10-01T00:00:00Z \
--end-time 2026-10-08T00:00:00Z \
--period 3600 \
--statistics Sum
A regional NAT gateway also needs the AvailabilityZone dimension. get-metric-statistics keeps one-minute data for 15 days, then five-minute data until 63 days, then hourly. Pull fine detail soon after an event.
To test the reading about downloads, add BytesInFromSource and BytesInFromDestination for a billing period and compare the total with NatGateway-Bytes usage for that gateway's Region. If only one gateway is in the Region, the two figures should be close, allowing for GB definitions and period edges. If NatGateway-Bytes matches only the upload figure, your bill contradicts this article's reading, and the bill wins.
A daily spike at the same hour usually suggests a scheduled job, and a flat rise suggests a change in steady traffic. Those are starting guesses for the next step.
Step 3: find who and what
The gateway's network interface is the place to capture. First get its interface ID and private addresses. A gateway can hold up to eight IPv4 addresses, so list them all. Written for this article from the describe-nat-gateways reference:
aws ec2 describe-nat-gateways \
--nat-gateway-ids nat-0123456789abcdef0 \
--query 'NatGateways[].NatGatewayAddresses[].[NetworkInterfaceId,PrivateIp]' \
--output text
The default flow log format is not enough. Without pkt-srcaddr and pkt-dstaddr you cannot join the private client to the external destination. Create a custom format, because a flow log's configuration cannot be changed after creation.
Written for this article from the create-flow-logs reference. Quote the format exactly as shown so the shell does not expand it:
aws ec2 create-flow-logs \
--resource-type NetworkInterface \
--resource-ids eni-0123456789abcdef0 \
--traffic-type ALL \
--max-aggregation-interval 60 \
--log-destination-type s3 \
--log-destination arn:aws:s3:::example-flow-log-bucket/nat-flows/ \
--log-format '${version} ${interface-id} ${srcaddr} ${dstaddr} ${pkt-srcaddr} ${pkt-dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status} ${flow-direction} ${traffic-path} ${pkt-src-aws-service} ${pkt-dst-aws-service}'
A regional NAT gateway uses --resource-type RegionalNatGateway. Its records show - for the interface, subnet and instance fields and carry the gateway ID in resource-id, so add ${resource-id} to the format. Its srcaddr matches pkt-srcaddr on outgoing traffic, so the two queries below need rewriting for that case.
Flow logs reach S3 in about 10 minutes, best effort. Capturing them does not affect network throughput or latency, but vended log charges apply. A flow log records only from its creation onwards. If none covered the period found in step 2, leave one running through the next occurrence or a comparable working period, then delete it if it is not needed.
Reading the records
On a zonal gateway, one connection appears as several records. AWS's example shows this, with client 10.0.1.5, gateway 10.0.0.220 and remote 203.0.113.5:
srcaddr |
dstaddr |
pkt-srcaddr |
pkt-dstaddr |
Leg |
|---|---|---|---|---|
| 10.0.1.5 | 10.0.0.220 | 10.0.1.5 | 203.0.113.5 | Client to gateway |
| 10.0.0.220 | 203.0.113.5 | 10.0.0.220 | 203.0.113.5 | Gateway to remote |
| 203.0.113.5 | 10.0.0.220 | 203.0.113.5 | 10.0.0.220 | Remote to gateway |
| 10.0.0.220 | 10.0.1.5 | 203.0.113.5 | 10.0.1.5 | Gateway to client |
Summing all records counts the same traffic twice. Choose one leg per direction. The first and last rows carry both the client and the true remote address, so use those.
Queries
Athena needs a table whose columns match the format above, in the same order. Start from AWS's table statement, remove columns not in your format, and add each date partition with its ALTER TABLE … ADD PARTITION statement. Hyphens in field names become underscores.
These two queries were written for this article. Replace the address, table name and date.
Uploads by client and real destination:
SELECT pkt_srcaddr AS client,
pkt_dstaddr AS remote,
pkt_dst_aws_service AS aws_service,
SUM(bytes) / 1073741824.0 AS gib
FROM nat_flow_logs
WHERE date = DATE('2026-10-03')
AND dstaddr IN ('10.0.0.220')
AND pkt_dstaddr NOT IN ('10.0.0.220')
GROUP BY 1, 2, 3
ORDER BY gib DESC
LIMIT 25;
Downloads by client and real source:
SELECT pkt_dstaddr AS client,
pkt_srcaddr AS remote,
pkt_src_aws_service AS aws_service,
SUM(bytes) / 1073741824.0 AS gib
FROM nat_flow_logs
WHERE date = DATE('2026-10-03')
AND srcaddr IN ('10.0.0.220')
AND pkt_srcaddr NOT IN ('10.0.0.220')
GROUP BY 1, 2, 3
ORDER BY gib DESC
LIMIT 25;
Interpret the result like this:
| Result | Meaning | Next step |
|---|---|---|
aws_service is S3 or DYNAMODB, same Region |
Traffic that a gateway endpoint can carry | Check the bucket or table Region, then consider an endpoint |
aws_service is a named service with an endpoint |
Possibly reachable through an interface endpoint | Confirm the service offers one in your Region |
aws_service is AMAZON, EC2, CLOUDFRONT or GLOBALACCELERATOR |
An address range, not an endpoint. A public EC2 address may be someone else's server |
Identify the destination before considering any endpoint |
aws_service is - and the remote is public |
Traffic to the internet, or an AWS address not labelled | Check against ip-ranges.json |
| Client subnet is in another AZ from the gateway | Cross-AZ charges are likely | Compare with DataTransfer-Regional-Bytes |
Compare the client's subnet AZ with the gateway's. A - means no label was attached to that record, and AWS describes these service fields as best effort, so treat a missing label as unknown. Where address ranges overlap, the field shows only one service. Our inference is that S3 traffic may therefore not carry the S3 label, so check ip-ranges.json before ruling S3 out.
Packet-level queries can also be run in CloudWatch Logs Insights. AWS gives examples for the default fields, and adds that attributing an instance to an internet destination needs the custom fields used here.
What to choose and why
These are editorial recommendations. The figures are hypothetical calculations at the Sydney rates above, using 730 hours a month and no discounts.
Traffic to S3 or DynamoDB in the same Region: use a gateway endpoint first. Gateway endpoints have no hourly or processing charge. At 10,240 GB a month through a NAT gateway, processing is 10,240 × US$0.059 = US$604.16. That line goes to zero for the moved traffic. Gateway endpoints are same-Region only. For S3 in another Region, AWS suggests an S3 interface endpoint, and cross-Region data transfer rates still apply.
A gateway endpoint change must name the VPC, same-Region service and every affected route table in a reviewed change plan. Rehearse policy behaviour and interruption recovery before production; no route-switch command is supplied here.
Read the warning under Risks when preparing that change. The route is added only to the route tables you select, so list every table used by the clients found in step 3.
Traffic to other AWS services: run the interface endpoint break-even. An interface endpoint is billed per hour in each AZ and per GB. In two AZs it costs 2 × US$0.013 × 730 = US$18.98 a month, and each GB moved saves US$0.059 − US$0.010 = US$0.049. Break-even is 18.98 ÷ 0.049, about 387 GB a month. In three AZs it is about 581 GB. Below that, leave the traffic on the NAT gateway. The calculation leaves out any data transfer on the path, and the NAT gateway hours continue if anything else needs it.
Clients crossing AZs to one gateway: compare against one gateway per AZ. A second public gateway with one address costs 730 × (US$0.059 + US$0.005) = US$46.72 a month. If cross-AZ traffic is billed at US$0.02 per GB, that pays for itself above 46.72 ÷ 0.02, about 2,336 GB a month across that path. Each secondary address adds US$3.65 a month. Processing is unchanged either way. Below break-even, the case rests on availability: a shared gateway's AZ failure takes internet access from the other AZs. A gateway only saves cross-AZ charges when routing sends resources to the gateway in their own AZ.
Internet egress: expect the smallest gains. At 2,048 GB out, processing is US$120.83, and data transfer at the first tier is US$233.47 before any free allowance. Endpoints cannot help when the destination is not an AWS service. Reduce the volume itself, or reconsider where the workload runs.
A regional NAT gateway is not a saving by itself. Its Sydney hourly and per-GB rates match the zonal ones, and it is charged per AZ. Three AZs cost the same hourly total as three zonal gateways. It also may take up to 60 minutes to expand into a new AZ, and until then traffic is processed across zones, so short-lived workloads in a new AZ can incur the cross-zone traffic this design is meant to avoid. Choose it for its operating model.
Risks and limits
Warning: Creating or modifying an S3 endpoint "switches network routes, and disconnects open TCP connections", and those connections do not resume. AWS recommends not running critical tasks at the time. Schedule it, and do not make this change during a backup window.
Warning: A gateway endpoint changes the source address S3 sees from public to private. AWS says
aws:SourceIpcannot be used for requests through the endpoint and to useaws:VpcSourceIpinstead. Check identity and bucket policies before creating the endpoint.Warning: Converting a zonal NAT gateway to regional resets existing connections. Plan a maintenance window, and check the two approaches AWS gives for keeping or changing public addresses.
Gateway endpoints have reach limits. They work only in the same Region and cannot be used from on-premises networks, peered VPCs in other Regions, or through a transit gateway.
Flow logs have gaps. Records can be skipped, shown as SKIPDATA in log-status. Traffic on a regional NAT gateway deleted minutes after creation is not captured, and neither is traffic to the Amazon DNS server. Treat the totals as evidence of who is responsible, and reconcile them with the CloudWatch byte metrics before quoting a saving.
Queries on the wrong leg double-count, or miss a whole direction. Test on one short period and compare the sum with BytesInFromSource or BytesInFromDestination for that period.
Check for performance limits before consolidating gateways. A NAT gateway supports 55,000 simultaneous connections per IPv4 address to each unique destination, starts at 5 Gbps and scales to 100 Gbps, and drops packets beyond its packet limit. Rising ErrorPortAllocation or PacketsDropCount values argue against funnelling more traffic through one gateway.
Prices change and discounts vary by account. AWS's pricing pages render rate tables dynamically, so this article used the Price List offers, whose publication dates are given above.
Costs from the analysis itself count too. Flow log delivery, S3 storage and Athena scans are charged. Parquet is faster to query and uses less storage than plain text, which helps on a long capture. The command above writes plain text. For Parquet, add --destination-options FileFormat=parquet when you create the flow log, and define the Athena table for Parquet instead of text.
Sources
Opened 2026-11-10. Sources behind the corrections reopened 10 November 2026.
- Amazon VPC pricing
- AWS Price List: EC2, ap-southeast-2
- AWS Price List: Data Transfer, ap-southeast-2
- AWS Price List: VPC, ap-southeast-2
- Amazon EC2 on-demand pricing
- AWS PrivateLink pricing
- Understanding data transfer charges (CUR)
- CUR 2.0 line item columns
- Cost Explorer filtering
- NAT gateway basics
- Regional NAT gateways
- Monitor NAT gateways with CloudWatch
- NAT gateway metrics and dimensions
- AWS services that publish CloudWatch metrics
- VPC Flow Logs
- Flow log records
- Flow log record examples
- Flow log limitations
- Create a flow log that publishes to S3
- Create the Athena table for flow logs
- AWS IP address ranges
- Gateway endpoints
- Gateway endpoints for Amazon S3
- AWS re:Post: find traffic sources through a NAT gateway
- AWS re:Post: reduce NAT gateway data transfer costs
- AWS CLI v2 reference:
get-cost-and-usage,get-metric-statistics,describe-nat-gateways,create-flow-logs,create-vpc-endpoint
This article was originally published on .