Australian cloud for business software — built and run in Sydney since 2003

NetLeverage
NetLeverage

NAT gateway and data transfer charges: tracing the traffic that makes the bill

Technical article
Cloud

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:SourceIp cannot be used for requests through the endpoint and to use aws:VpcSourceIp instead. 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.