If your private subnets send S3 traffic through a NAT Gateway, you are probably paying a per-GB processing fee for data that never needed to take that path. An S3 endpoint can remove that charge for eligible traffic. But S3 offers two endpoint types, and the wrong pick either leaves a client unable to connect or adds an hourly bill you didn't need.
This guide skips the general NAT-versus-endpoints overview and answers the narrower question: for S3 traffic, do you need the Gateway Endpoint, the Interface Endpoint (AWS PrivateLink), or both? The short version, from this dev.to walkthrough of NAT and endpoint scenarios: start with the Gateway Endpoint for ordinary in-VPC access, and add an Interface Endpoint only when clients outside the VPC need private S3 access. Pricing and limits change, so confirm both in the current AWS documentation before you commit.
Access scope: who needs to reach S3?
This question decides most cases. A Gateway Endpoint serves workloads inside the VPC where you create it, and the bucket should be in the same AWS Region. The dev.to walkthrough states that it cannot directly serve clients from on-premises networks, peered VPCs, or networks connected through a Transit Gateway.
An Interface Endpoint creates elastic network interfaces (ENIs) with private IP addresses in the subnets you choose. With the right routes, DNS configuration, and security group rules, connected on-premises systems and other VPCs can reach those addresses.
The practical rule: if every S3 client lives in the VPC that owns the endpoint, the Gateway Endpoint is enough. If a backup server in your data center, a peered VPC, or a Transit Gateway attachment must reach S3 privately, the Gateway Endpoint alone won't do it.
Side-by-side comparison
| Factor | S3 Gateway Endpoint | S3 Interface Endpoint | |---|---|---| | Access scope | Clients in the same VPC, bucket in the same Region | In-VPC clients, plus on-premises, peered VPC, or Transit Gateway clients with suitable routing and DNS | | Network mechanism | Route table entry for the S3 managed prefix list | ENIs with private IPs in your subnets (PrivateLink) | | ENIs and security groups | None to manage | Yes, security groups on the ENIs (commonly allow TCP 443) | | DNS | Clients keep using normal S3 names; the route table steers traffic | Endpoint-specific DNS names or private DNS settings; on-premises clients need DNS that resolves to the endpoint | | Cost model | No additional endpoint charge | Hourly charge per enabled Availability Zone plus data processing | | Main limitation | No direct on-premises, peered VPC, or Transit Gateway access | More moving parts and a recurring cost |
The dev.to walkthrough supports the cost and ENI rows. It says little about DNS, so verify that row against the current AWS documentation for your configuration. Normal S3 storage and request charges apply either way.
Routing: why the prefix-list route beats 0.0.0.0/0
A route table picks the most specific matching route. When you associate a Gateway Endpoint with a route table, AWS adds a route whose destination is the S3 managed prefix list and whose target is the endpoint. That entry is more specific than 0.0.0.0/0, so S3 traffic takes the endpoint while everything else still goes to the NAT Gateway.
Here is an illustrative route table. The IDs are placeholders, and the prefix-list entry stands in for the S3 address ranges.
BEFORE (private subnet route table)
Destination Target
10.0.0.0/16 local
0.0.0.0/0 nat-0123456789abcdef0
AFTER (Gateway Endpoint associated)
Destination Target
10.0.0.0/16 local
pl-xxxxxxxx (S3) vpce-0123456789abcdef0
0.0.0.0/0 nat-0123456789abcdef0
Nothing changes in the application. The instance still calls the regular S3 endpoint name, and only the route decision differs. The endpoint applies only to route tables you associate it with. A private subnet on a different route table stays on the NAT path until you add that table.
The Interface Endpoint works differently. It adds no route-table entry. Clients resolve a name to a private IP in your subnet, and the local VPC route (or your VPN, Direct Connect, or Transit Gateway routes) carries the traffic there.
DNS and security groups for clients outside the VPC
On-premises, peered, and Transit Gateway clients need two things the Gateway Endpoint never asks of you. First, their DNS must return the endpoint ENI addresses, or the application must target the endpoint-specific DNS name. Second, the endpoint's security group must allow inbound HTTPS from those client networks, and your routes must reach the endpoint subnets. For more on this, see more on filesystem mcp server in claude desktop: safe setup guide.
If you run both endpoint types, read the AWS documentation on private DNS for S3 Interface Endpoints. It describes a setting intended to apply the endpoint DNS behavior to inbound resolver traffic only, so in-VPC workloads can keep using the free Gateway Endpoint. That pattern lets one VPC serve both audiences without moving in-VPC traffic onto a paid path. Confirm the exact setting name and behavior in the current docs.
Below is a minimal Terraform sketch of both endpoints. It is unexecuted. The provider version is not pinned and all IDs are placeholders.
## UNEXECUTED example. Adjust region, IDs, and provider version.
resource "aws_vpc_endpoint" "s3_gateway" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.us-east-1.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = var.private_route_table_ids
}
resource "aws_vpc_endpoint" "s3_interface" {
vpc_id = var.vpc_id
service_name = "com.amazonaws.us-east-1.s3"
vpc_endpoint_type = "Interface"
subnet_ids = var.endpoint_subnet_ids
security_group_ids = [var.endpoint_sg_id]
private_dns_enabled = false # decide deliberately; see DNS notes above
}
Cost: where the savings come from
According to the dev.to walkthrough, the Gateway Endpoint carries no additional endpoint charge. It also moves eligible S3 traffic off the NAT Gateway, so that traffic no longer counts toward NAT data processing. That is the saving small teams usually want. See a closer look at how to compare software development quotes like for like for additional background.
The Interface Endpoint charges hourly per enabled Availability Zone, plus data processing. For workloads that need S3 only inside one VPC, the walkthrough says the extra expense may not be justified. Put plainly, using an Interface Endpoint for in-VPC traffic when a Gateway Endpoint would work swaps a free path for a paid one. This article quotes no rates, so check current regional pricing on the AWS pricing pages.
Verify that S3 traffic skips the NAT Gateway
Don't trust the console alone. Run checks that show the path. These commands are unexecuted examples with placeholder IDs.
## 1. Confirm the endpoint route exists in the subnet's route table
aws ec2 describe-route-tables --route-table-ids rtb-0123456789abcdef0 \
--query "RouteTables[].Routes"
## 2. Confirm the endpoint is available and associated
aws ec2 describe-vpc-endpoints --filters Name=service-name,Values=com.amazonaws.us-east-1.s3
## 3. From the instance: test S3 access in the bucket's Region
aws s3 ls s3://example-bucket --region us-east-1
## 4. Check DNS resolution from the instance
dig +short s3.us-east-1.amazonaws.com
Step 1 should show a route with a prefix-list destination and a vpce- target. Step 3 proves only that access works, not which path it took.
A traceroute is a weak test here, because AWS networking typically does not expose hops the way a physical network does. Better evidence comes from other places:
- NAT Gateway metrics: CloudWatch metrics should show S3-bound bytes dropping after the change.
- VPC Flow Logs: logs on the instance's interface can show whether S3 flows still pass through the NAT.
- CloudTrail: S3 data events can record the VPC endpoint ID on requests that arrive through an endpoint (confirm this field in the current docs).
Common failure: AccessDenied after the endpoint works
A working route does not guarantee authorization. Endpoint policies, bucket policies, and IAM permissions all apply, and any one of them can deny a request. The dev.to walkthrough warns that a bucket-policy condition requiring access through a particular endpoint can also block legitimate access through other paths.
Also read: go http server graceful shutdown: fix the common bugs
A typical case: a bucket policy denies everything unless aws:SourceVpce matches the Gateway Endpoint. The in-VPC app works. Then the on-premises server connects through the Interface Endpoint, which has a different endpoint ID, and every request fails with AccessDenied. Add the Interface Endpoint ID to the allowed list, or restructure the condition.
Also watch for conditions based on source IP. Traffic that arrives through a VPC endpoint uses private addressing, so aws:SourceIp rules do not match it the way they do for public traffic (confirm in the S3 documentation). When something fails, check these together:
- the endpoint policy
- the bucket policy
- the instance role
- the security groups on any Interface Endpoint
- DNS resolution
Decision checklist
Work through these questions in order:
- Do all S3 clients run inside one VPC, with buckets in the same Region? Use a Gateway Endpoint and associate it with every private route table that needs it.
- Does an on-premises server need private S3 access? Add an Interface Endpoint, plus DNS, routing, and security group rules.
- Do clients in a peered VPC or behind a Transit Gateway need S3? Add an Interface Endpoint, and verify routing and DNS from those networks.
- Do you have both audiences? Run both endpoints, and review the private DNS options so in-VPC traffic stays on the Gateway Endpoint.
- Does the workload also need public repositories or APIs? Keep the NAT Gateway for that traffic only.
A team running EC2 or containers in private subnets that only uploads logs and backups to S3 in its own Region should create the Gateway Endpoint and stop there. A team with an on-premises backup server, a hub-and-spoke Transit Gateway, or peered VPCs that need S3 should add an Interface Endpoint for those clients and keep the Gateway Endpoint for the in-VPC path. Before production, confirm current pricing, quotas, and DNS behavior in the AWS documentation.



