Symptom
NatGateway-Bytes is one of the largest EC2-Other lines. Private subnets pull container images, write logs and read S3 through the NAT all day.
1. See how much flows through each NAT
CloudWatch metrics per NAT Gateway: BytesOutToDestination, BytesInFromDestination, BytesOutToSource, BytesInFromSource. Data processing is charged on bytes through the gateway in either direction.
2. Find the destinations
With VPC Flow Logs (custom format including pkt-dstaddr, pkt-srcaddr, bytes) in S3 + Athena, for flows through the NAT ENI:
SELECT pkt_dstaddr, SUM(bytes) / 1e9 AS gb
FROM vpc_flow_logs
WHERE interface_id = 'eni-0nat1234567890' -- the NAT gateway's ENI
AND day >= '2026/10/18'
GROUP BY pkt_dstaddr
ORDER BY gb DESC
LIMIT 25;
Map the top IPs with the AWS IP ranges file (ip-ranges.json) to see which are S3, DynamoDB, ECR, CloudWatch, or the internet.
3. Gateway endpoints: S3 and DynamoDB (no charge)
aws ec2 create-vpc-endpoint --vpc-id vpc-0abc --vpc-endpoint-type Gateway \
--service-name com.amazonaws.eu-west-1.s3 --route-table-ids rtb-0priv1 rtb-0priv2 rtb-0priv3
aws ec2 create-vpc-endpoint --vpc-id vpc-0abc --vpc-endpoint-type Gateway \
--service-name com.amazonaws.eu-west-1.dynamodb --route-table-ids rtb-0priv1 rtb-0priv2 rtb-0priv3
Gateway endpoints add routes to the prefix lists in the chosen route tables. They work for same-region buckets/tables only. Add an endpoint policy if you want to restrict which buckets can be reached (also a data exfiltration control). Check bucket policies that rely on aws:SourceIp; traffic via the endpoint uses aws:SourceVpce instead.
4. Interface endpoints: when they pay off
Interface endpoints charge per AZ per hour plus per GB processed (lower than NAT processing). Worth it for high-volume services:
| Service | Endpoint(s) |
|---|---|
| ECR image pulls | ecr.api, ecr.dkr (+ S3 gateway endpoint for layers) |
| CloudWatch Logs | logs |
| STS, Secrets Manager, KMS, SSM | sts, secretsmanager, kms, ssm, ssmmessages, ec2messages |
| SQS, SNS, Kinesis | sqs, sns, kinesis-streams |
Break-even: compare (hourly cost × AZs × hours) against GB/month × (NAT rate − endpoint rate). Low-volume services may be cheaper through the NAT. In multi-account setups, centralise shared interface endpoints in a shared-services VPC with Route 53 private hosted zones, if your design allows.
5. Fix cross-AZ NAT paths
Each AZ's private route table should point to the NAT Gateway in the same AZ. Routing all AZs to one NAT adds cross-AZ data transfer charges and creates a single-AZ dependency.
aws ec2 describe-route-tables --filters Name=vpc-id,Values=vpc-0abc \
--query 'RouteTables[].{rt:RouteTableId,nat:Routes[?NatGatewayId].NatGatewayId|[0],subnets:Associations[].SubnetId}'
6. Reduce the traffic itself
- Cache container images (ECR pull-through cache, larger node image caches, fewer node replacements).
- Avoid re-downloading packages on every instance boot. Bake AMIs or use internal mirrors.
- Compress logs and payloads.
7. Measure
Compare NatGateway-Bytes cost and NAT metrics two weeks before and after each change.
Warning
Adding a gateway endpoint changes the path for all S3 traffic from those subnets. Cross-region bucket access still needs the NAT, and bucket policies with IP conditions may start denying requests. Test with a single route table first.
Checklist
- NAT bytes measured per gateway
- Top destinations from flow logs mapped
- S3 and DynamoDB gateway endpoints on all private route tables
- Interface endpoints where break-even makes sense
- Same-AZ NAT routing
- Traffic reduced at the source; before/after cost compared