# NAT Gateway Processing S3 Traffic, Use Gateway Endpoint

> Why ZopNight raises no finding for S3 traffic through NAT gateways, and a manual method to confirm and fix it.

Source: https://zop.dev/integrations/aws/recommendations/nat-gateway-processing-s3-traffic-use-gateway-endpoint

---

## What this check was meant to catch

Workloads in private subnets often reach Amazon S3 through a NAT gateway simply because that is
the default route to anything outside the VPC. Each of those bytes incurs the NAT gateway's data
processing charge. The [VPC pricing example](https://aws.amazon.com/vpc/pricing/) walks through a
1 GB upload from an instance to S3 in the same Region: the transfer itself is free, yet the NAT
gateway still charges $0.045 for processing it, and AWS notes that a gateway VPC endpoint would have
avoided that charge.

## Current status: no finding is emitted

ZopNight has switched this check off for every NAT gateway. The earlier version produced an
advisory with no dollar figure attached, which did not meet ZopNight's standard that a cost finding
either carries a concrete number or is not shown. The number needed is the S3 share of the
gateway's traffic, and neither the NAT gateway's CloudWatch metrics nor ZopNight's inventory expose
it. A sibling page,
<a href="https://zop.dev/integrations/aws/recommendations/nat-gateway-high-outbound-traffic-consider-a-gateway-endpoint">NAT Gateway High Outbound Traffic, Consider a Gateway Endpoint</a>,
explains the same gap for S3 and DynamoDB together and shows how to measure the split with Flow Logs.

## Tracing which subnets use a NAT gateway

Start from the gateway and work back to the subnets whose default route points at it:

```bash
aws ec2 describe-route-tables \
  --filters Name=route.nat-gateway-id,Values=nat-0123456789abcdef0 \
  --query 'RouteTables[].[RouteTableId,VpcId,Associations[].SubnetId]'
```

Then check whether those same route tables already have a route to the S3 prefix list, which is
what a gateway endpoint adds:

```bash
aws ec2 describe-route-tables \
  --filters Name=route.nat-gateway-id,Values=nat-0123456789abcdef0 \
  --query 'RouteTables[].Routes[?DestinationPrefixListId!=`null`].[DestinationPrefixListId,GatewayId]'
```

No prefix-list route means S3 traffic from those subnets is going through the NAT gateway.

## What decides whether the change is worth it

The saving scales with S3 volume. A subnet that moves a few gigabytes a month saves cents; a data
pipeline reading terabytes from S3 saves real money. [Gateway endpoints](https://docs.aws.amazon.com/vpc/latest/privatelink/gateway-endpoints.html)
carry no additional charge, so there is no break-even point, only a question of whether the change is
worth an engineer's time.

```text
monthly saving = S3-bound GB through the NAT gateway per month x NAT processing rate per GB
```

## Moving S3 traffic to a gateway endpoint

1. Create an S3 gateway endpoint in the VPC:
   `aws ec2 create-vpc-endpoint --vpc-id vpc-0abc --service-name com.amazonaws.us-east-1.s3 --route-table-ids rtb-0abc`.
2. Review bucket policies that allow access only from the NAT gateway's public IP. Requests through
   the endpoint arrive from private addresses, so restrict by endpoint with `aws:sourceVpce` instead.
3. Confirm the new prefix-list route appears in each private route table.

**Warning**
AWS notes that adding the endpoint switches network routes and disconnects open TCP connections, so schedule the change outside a busy data transfer.
