Private subnets are one of the first good habits you learn in AWS. Put your EC2 instances, containers and databases where the internet cannot start a connection to them, and you remove a large class of risk.
The catch is that private workloads still need to talk to things: operating system repositories, third-party APIs, and AWS services such as Amazon S3. AWS gives you more than one way to make that happen, and they are not interchangeable.
This article walks through three tools:
- NAT Gateway for outbound access from private subnets.
- S3 Gateway Endpoint for free, route-table-based access to S3 from inside one VPC.
- S3 Interface Endpoint (AWS PrivateLink) for private S3 access that can extend beyond the VPC, for example to on-premises networks.
By the end you should be able to look at a requirement and pick the right one, and explain why.
Note on sources. Technical statements here are based on the official AWS documentation and blog posts listed in the References section. Prices change and differ by Region, so I describe the cost model and point you to the AWS pricing pages instead of quoting numbers as universal facts. Console labels may change over time.
Understanding AWS VPC networking
A quick vocabulary pass before the comparisons.
- VPC: your isolated virtual network in AWS, defined by one or more CIDR ranges.
- Public subnet: a subnet whose route table has a route to an Internet Gateway (IGW).
- Private subnet: a subnet with no direct route to an IGW. Its resources use private IP addresses only.
- Route table: the list of rules that decides where traffic leaves a subnet. The most specific matching route wins.
- Internet Gateway: the VPC component that connects public subnets to the internet. It handles the mapping between a public IPv4 address and a private one for resources that have a public address.
- NAT Gateway: a managed service that lets resources in a private subnet start outbound connections without exposing them to unsolicited inbound traffic.
- ENI (Elastic Network Interface): a virtual network card with a private IP address. Interface endpoints are built from ENIs.
How does a private instance reach an AWS service?
An EC2 instance in a private subnet that runs aws s3 cp has three realistic options:
- Go out through a NAT Gateway to the service's public endpoint.
- Use an S3 Gateway Endpoint, selected by a route in the subnet's route table.
- Use an S3 Interface Endpoint, reached at a private IP address inside the VPC.
flowchart LR
EC2["Private EC2 instance"] --> RT{"Private route table"}
RT -->|"0.0.0.0/0"| NAT["NAT Gateway (public subnet)"]
NAT --> IGW["Internet Gateway"]
IGW --> NET["Public internet / external service"]
RT -->|"S3 prefix list"| GWE["S3 Gateway Endpoint"]
GWE --> S3A["Amazon S3 (same Region)"]
RT -->|"VPC CIDR (local)"| IFE["S3 Interface Endpoint (ENI + private IP)"]
IFE --> S3B["Amazon S3 via PrivateLink"]
Figure 1. Three exit paths from a private subnet. The route table, plus DNS for the interface endpoint, decides which one is used.
What is an AWS NAT Gateway?
A NAT Gateway performs network address translation for resources in private subnets. Instances keep private IPv4 addresses. The NAT Gateway replaces the source address of outgoing packets and translates replies back.
Public vs private NAT Gateway
According to the AWS documentation, a NAT Gateway has one of two connectivity types:
| Public NAT Gateway (default) | Private NAT Gateway | |
|---|---|---|
| Purpose | Outbound internet access from private subnets | Connectivity to other VPCs or on-premises networks |
| Elastic IP | Required, associated at creation | Cannot be associated |
| Internet Gateway | Used for internet access | If traffic is sent to an IGW, the IGW drops it |
| Placement | In a public subnet | In a subnet, routed via a transit gateway or virtual private gateway |
| Inbound connections | No unsolicited inbound connections from the internet | No unsolicited inbound connections from the connected networks |
Two points trip people up:
- A public NAT Gateway needs an Elastic IP and a route toward an Internet Gateway for internet access.
- A private NAT Gateway does not give you internet access. It is for translating addresses toward other private networks, for example when CIDR ranges overlap.
Traffic flow
sequenceDiagram
participant I as Private EC2
participant R as Private route table
participant N as NAT Gateway (public subnet)
participant G as Internet Gateway
participant E as External service
I->>R: Packet to 203.0.113.10
R->>N: 0.0.0.0/0 targets the NAT Gateway
N->>G: Source translated to NAT address
G->>E: Source becomes the Elastic IP
E-->>G: Response
G-->>N: Translated back
N-->>I: Delivered to the original instance
Figure 2. Outbound flow through a public NAT Gateway. The external address 203.0.113.10 is a documentation example.
The setup requires:
- A NAT Gateway in a public subnet, with an Elastic IP.
- The public subnet's route table has
0.0.0.0/0pointing to the IGW. - The private subnet's route table has
0.0.0.0/0pointing to the NAT Gateway.
Availability and design
Classic (zonal) NAT Gateways live in one Availability Zone. For resilience, a common design is one NAT Gateway per AZ, with each private route table pointing to the NAT Gateway in its own AZ. This also avoids sending traffic across AZs just to reach NAT, which can add data transfer cost.
AWS also documents regional NAT Gateways, which expand across the AZs where you have workloads and simplify routing. They do not support private connectivity, so private NAT still uses zonal gateways. Check the documentation for current Region availability and limits before choosing.
Where NAT is the right tool
- Downloading OS updates and packages from public repositories.
- Calling third-party SaaS APIs.
- Any destination on the public internet that has no private path.
Limits to keep in mind
- It is billed by time and by data processed (see the cost section), so heavy traffic to AWS services through NAT can become expensive.
- It is outbound-only. It is not a way to publish services.
- Traffic to S3 through NAT leaves your VPC through the internet path, even though S3 is an AWS service. That is often not what security teams want.
What is a VPC endpoint?
A VPC endpoint lets resources in your VPC connect privately to supported AWS services (and to services powered by PrivateLink) without needing an internet gateway, NAT device, or VPN for that traffic.
There are two types relevant here:
- Gateway endpoint. A target in your route table. Available only for Amazon S3 and Amazon DynamoDB.
- Interface endpoint (AWS PrivateLink). One or more ENIs with private IPs in your subnets, protected by security groups. Available for many AWS services and for partner or your own PrivateLink services.
Both support endpoint policies, which control which principals can do which actions on which resources through the endpoint. The default policy is full access, so tightening it is worth doing.
S3 Gateway Endpoint vs S3 Interface Endpoint
This is the part that causes most confusion, because both are "an endpoint for S3" and both keep traffic on the AWS network.
S3 Gateway Endpoint
How it works:
- You create the endpoint in a VPC and associate it with route tables.
- AWS adds a route that sends S3-bound traffic to the endpoint, using the S3 managed prefix list. In the route table you will see the prefix list ID as the destination and the endpoint ID (
vpce-...) as the target. - There is no ENI and no endpoint security group. In security groups you can reference the prefix list ID. Network ACLs cannot reference prefix lists, so use the IP ranges instead.
- The AWS documentation states there is no additional charge for gateway endpoints.
- The endpoint must be in the same Region as the S3 buckets.
Limits:
- The documentation says gateway endpoints do not allow access from on-premises networks.
- They also do not allow access from peered VPCs in other Regions or through a transit gateway. The endpoint connection cannot be extended out of the VPC, so resources across VPN, VPC peering, Transit Gateway or Direct Connect cannot use it.
- For Amazon-provided DNS, both DNS hostnames and DNS resolution must be enabled on the VPC.
-
aws:SourceIpcannot be used in policies for requests that traverse a VPC endpoint. Useaws:VpcSourceIpor the VPC/endpoint conditions instead.
S3 Interface Endpoint
How it works:
- PrivateLink creates endpoint ENIs with private IPs in the subnets you choose (typically one per AZ).
- Security groups on those ENIs control who can connect, typically TCP 443.
- Each endpoint gets regional and zonal DNS names that resolve to the endpoint's private IPs. For S3 you reach buckets with a bucket-prefixed name such as
bucket.vpce-<id>-<suffix>.s3.<region>.vpce.amazonaws.com. You can point the AWS CLI at it with--endpoint-url. - Because the endpoint is just private IPs in your network, on-premises systems connected over Direct Connect (private VIF) or Site-to-Site VPN can reach it, as AWS describes in its hybrid access guidance.
- Other VPCs can reach the endpoint IPs over routed connectivity such as Transit Gateway or peering, provided routing, DNS and security groups allow it.
- Cost model: hourly charge per endpoint per AZ, plus a per-GB data processing charge.
The DNS wrinkle (worth reading twice)
The S3 interface endpoint supports a Private DNS option with a special mode: private DNS only for the inbound Resolver endpoint. According to AWS:
- With this mode, on-premises DNS queries (forwarded to a Route 53 Resolver inbound endpoint) resolve S3 names to the interface endpoint's private IPs.
- Queries from inside the VPC resolve to S3's public IPs, and those requests use the gateway endpoint (no extra charge).
- This mode requires an S3 gateway endpoint in the same VPC.
- If you enable private DNS for all traffic instead, VPC traffic also goes through the interface endpoint and you pay interface endpoint data processing for it.
This is the cost-friendly hybrid pattern: gateway endpoint for workloads in the VPC, interface endpoint for on-premises and other networks.
Comparison table
| S3 Gateway Endpoint | S3 Interface Endpoint | |
|---|---|---|
| Connectivity model | Route table target using S3 prefix list | ENI with private IPs (PrivateLink) |
| Route table requirement | Yes, route per associated route table | No special S3 route; normal VPC routing to the ENI IPs |
| ENI / private IP | No | Yes, per subnet/AZ |
| Security groups | No (use prefix list in your own SGs; policy controls on the endpoint) | Yes, on the endpoint ENIs |
| On-premises access | Not supported | Supported over Direct Connect or VPN, with DNS set up |
| Cross-VPC / Transit Gateway | Not supported | Possible with correct routing, DNS and security |
| DNS | Standard S3 names; VPC DNS settings required | Endpoint-specific names; optional private DNS (inbound-only mode needs a gateway endpoint) |
| Cost model | No additional endpoint charge | Hourly per AZ plus per-GB processing |
| Typical use | Workloads inside one VPC | Hybrid access, shared-services VPCs, strict private access designs |
They are not interchangeable. If an on-premises server needs private S3 access, a gateway endpoint will not do it. If a workload inside the VPC only needs cheap private S3 access, an interface endpoint is usually unnecessary spend.
Understanding the architecture image
![Architecture diagram comparing an S3 Gateway Endpoint, which routes traffic from a private subnet through a route table prefix list and cannot be reached from on-premises or peered VPCs, with an S3 Interface Endpoint, which uses an ENI and security group reachable from on-premises servers and spoke VPCs via Transit Gateway.]
Figure 3. Gateway vs Interface endpoint for S3. Place this image right after this heading.
The image uses four numbered callouts. Here is each one, with a few checks.
1. Route injected (Gateway). The route table gets an entry whose destination is the S3 prefix list and whose target is the gateway endpoint. Traffic to S3 follows that route; everything else follows the other routes. This matches the AWS documentation. One refinement: the route is added when you associate the route table with the endpoint, and removed when you disassociate it.
2. Gateway = $0. The AWS documentation says there is no additional charge for gateway endpoints. Note that this refers to the endpoint itself. Normal S3 request and storage charges still apply, and other data transfer charges can apply in specific cases such as cross-Region access.
3. Interface = ENI + SG. The interface endpoint is an ENI with a private IP, protected by a security group that you configure (for example, allow HTTPS 443 from your sources). The image abbreviates the hostname as bucket.vpce-....s3.amazonaws.com. The real endpoint-specific name follows the pattern bucket.vpce-<id>-<suffix>.s3.<region>.vpce.amazonaws.com, so copy it from your endpoint's details rather than from the picture.
4. Transitive routing. The image shows on-premises servers (Direct Connect or VPN) and spoke VPCs (via Transit Gateway) reaching the interface endpoint. That is achievable, but "transitive" is shorthand. It works because the endpoint is a private IP in your network, so anything with a route to that IP, DNS that resolves to it, and security group permission can connect. An interface endpoint does not make every connected network able to reach S3 automatically.
The red "no route in" warning. This shows that on-premises and peered networks cannot use the gateway endpoint. The AWS documentation confirms the limitation, and the underlying reason is that gateway endpoints cannot be extended out of the VPC. It is less about a missing route and more about how the feature works.
Two more assumptions to be aware of:
- The image says "reachable from any routed network". In practice you also need DNS and security groups to line up, and the AWS hybrid guidance describes specific DNS steps for on-premises.
- The image shows a single S3 bucket. Remember the endpoint must be in the same Region as the bucket for the gateway type.
NAT Gateway vs VPC endpoint
| NAT Gateway | VPC Endpoint | |
|---|---|---|
| Main purpose | Outbound connectivity with address translation | Private connectivity to supported AWS and PrivateLink services |
| Traffic destination | Public internet (public NAT) or other private networks (private NAT) | A specific AWS service or PrivateLink service |
| Internet dependency | Public NAT needs an IGW and Elastic IP | None for the endpoint path itself |
| Security | Blocks unsolicited inbound; outbound is broad unless filtered | Endpoint policies, security groups (interface), bucket and IAM conditions |
| Routing | Default route (0.0.0.0/0) to the NAT Gateway |
Gateway: prefix list route. Interface: private IPs and DNS |
| Cost model | Hourly plus per-GB processed (check AWS pricing) | Gateway: no additional charge. Interface: hourly per AZ plus per GB |
| Common use | OS updates, third-party APIs | S3, DynamoDB and other AWS services without internet egress |
They solve different problems, and you will often use them together: NAT for the internet, endpoints for AWS services.
A real-world production architecture
Requirements for an example company:
- Private EC2 instances download OS updates and call external repositories.
- Logs and backups go to Amazon S3, kept on a private AWS path.
- Selected on-premises systems also need private S3 access.
- NAT data-processing costs should stay low.
flowchart TB
subgraph ONPREM["On-premises"]
SRV["Backup servers"]
DNS["On-prem DNS (conditional forwarder)"]
end
subgraph VPC["Application VPC (2 AZs)"]
subgraph PUB["Public subnets"]
NATA["NAT Gateway AZ-a"]
NATB["NAT Gateway AZ-b"]
end
subgraph PRIV["Private subnets"]
EC2A["EC2 AZ-a"]
EC2B["EC2 AZ-b"]
end
subgraph ENDPOINTS["Endpoints"]
GWE["S3 Gateway Endpoint"]
IFE["S3 Interface Endpoint (ENIs, SG: 443 from on-prem)"]
RES["Route 53 Resolver inbound endpoint"]
end
IGW["Internet Gateway"]
end
S3[("Amazon S3 buckets")]
NET["Public repositories / external APIs"]
EC2A -->|"S3 prefix list"| GWE
EC2B -->|"S3 prefix list"| GWE
GWE --> S3
EC2A -->|"0.0.0.0/0"| NATA
EC2B -->|"0.0.0.0/0"| NATB
NATA --> IGW
NATB --> IGW
IGW --> NET
SRV -->|"Direct Connect or VPN"| IFE
DNS -.->|"S3 domain forwarded"| RES
IFE --> S3
Figure 4. Gateway endpoint for in-VPC S3 traffic, NAT for the internet, and an interface endpoint for on-premises S3 access.
Which traffic uses what
| Traffic | Path | Why |
|---|---|---|
| OS updates, external repositories | NAT Gateway | Public internet destinations |
| EC2 to S3 (logs, backups) | S3 Gateway Endpoint | No additional endpoint charge, stays on the AWS network |
| On-premises to S3 | S3 Interface Endpoint | Gateway endpoints are not reachable from on-premises |
Design points
- Multi-AZ. One NAT Gateway per AZ with matching route tables, and interface endpoint ENIs in each AZ you use. Alternatively evaluate a regional NAT Gateway.
- Routing. The gateway endpoint's S3 prefix-list route covers S3 public IP ranges, which are more specific than the default route, so S3 traffic is steered to the endpoint instead of NAT.
- DNS. Use the inbound-resolver-only private DNS option on the interface endpoint, with a gateway endpoint in the VPC. Forward the S3 domain from on-premises DNS to the Route 53 Resolver inbound endpoint.
- Security groups. On the interface endpoint, allow TCP 443 only from the on-premises ranges (and other networks you intend to allow).
- IAM. Instances use instance profiles (roles), never long-lived keys. Grant only the S3 actions and prefixes they need.
-
Bucket policy. Restrict the bucket to your endpoints with
aws:sourceVpceoraws:sourceVpc. Be careful: AWS notes that a policy limiting access to a specific endpoint also blocks console access. -
Endpoint policy. Limit the endpoint to the buckets you own, for example with
s3:ResourceAccount.
When is the S3 interface endpoint justified? When something outside the VPC needs private S3 access: on-premises systems, or other VPCs and Regions over private links. If everything is inside one VPC, the gateway endpoint is usually enough.
Practical implementation
Console labels and options change over time, so treat the names below as a guide.
S3 Gateway Endpoint (console)
- Open the VPC console.
- Go to Endpoints and choose Create endpoint.
- Choose the AWS services category and search for
com.amazonaws.<region>.s3. - Select the service entry whose type is Gateway.
- Select the correct VPC.
- Select the private route tables used by your private subnets.
- Set an endpoint policy. Replace the default full-access policy with one limited to your buckets.
- Create the endpoint, then confirm the route table now shows an S3 prefix list route targeting the
vpce-ID. - From a private instance, run
aws s3 ls s3://<your-bucket> --region <region>using an instance role. Check the result using the route table, VPC Flow Logs and, if needed, CloudTrail.
S3 Gateway Endpoint (AWS CLI)
Replace everything in angle brackets with your own values.
aws ec2 create-vpc-endpoint \
--vpc-id <vpc-id> \
--vpc-endpoint-type Gateway \
--service-name com.amazonaws.<region>.s3 \
--route-table-ids <rtb-id-1> <rtb-id-2>
S3 Interface Endpoint (console)
- In Endpoints, choose Create endpoint and search for the S3 service for your Region.
- Choose the Interface type entry (S3 lists both types).
- Select the VPC and subnets in each AZ you need.
- Attach a security group allowing inbound TCP 443 from the networks that should connect.
- Configure DNS to match your design: either endpoint-specific names only, or private DNS. For the hybrid pattern, choose the inbound-resolver-only option, which needs the gateway endpoint in the same VPC.
- Build the DNS path for on-premises: a Route 53 Resolver inbound endpoint in the VPC, and a conditional forwarder on your on-premises DNS.
- Confirm routing from on-premises (Direct Connect or VPN) to the endpoint subnets.
- Test and review logs.
S3 Interface Endpoint (AWS CLI sketch)
aws ec2 create-vpc-endpoint \
--vpc-id <vpc-id> \
--vpc-endpoint-type Interface \
--service-name com.amazonaws.<region>.s3 \
--subnet-ids <subnet-id-a> <subnet-id-b> \
--security-group-ids <sg-id> \
--private-dns-enabled \
--dns-options PrivateDnsOnlyForInboundResolverEndpoint=true
Check the current create-vpc-endpoint reference for the exact option names before using this in a real account.
To test through the endpoint-specific DNS name from a machine with network access to the endpoint:
aws s3 ls s3://<your-bucket> \
--region <region> \
--endpoint-url https://bucket.<vpce-dns-name-from-the-console>
Copy <vpce-dns-name-from-the-console> from the endpoint's Details page. Do not paste credentials into commands or scripts. Use roles or your normal credential provider.
Example bucket policy that denies access outside a specific gateway endpoint (adapt carefully, and test with a non-critical bucket first because it can lock you out of the console):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyUnlessFromMyEndpoint",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::<your-bucket>",
"arn:aws:s3:::<your-bucket>/*"
],
"Condition": {
"StringNotEquals": {
"aws:sourceVpce": "<vpce-id>"
}
}
}
]
}
Security best practices
- Use IAM roles, not hard-coded access keys. Instance profiles and task roles avoid secrets in code.
- Least privilege. Scope IAM policies, endpoint policies and bucket policies to the buckets and actions required.
- Layer your controls. IAM says who, the endpoint policy says what can pass through this door, the bucket policy says who may enter the bucket.
-
Use HTTPS/TLS. Enforce it with a bucket policy condition on
aws:SecureTransport. - Lock down interface endpoint security groups. Allow 443 only from known sources.
- Log and review. VPC Flow Logs show network flows. CloudTrail records API activity.
- Avoid unintended public access. Keep S3 Block Public Access on unless you have a reviewed reason.
- Review cross-account and cross-network access carefully, especially when many VPCs and on-premises ranges can reach one shared interface endpoint.
Cost optimization
Cost is the main reason many teams adopt endpoints, so here is the model.
- NAT Gateway. Billed for each hour it is available and for each GB of data it processes. Check the VPC pricing page for your Region. Heavy S3 traffic sent through NAT pays the processing charge on every GB.
- S3 Gateway Endpoint. AWS documents no additional charge for the endpoint. Moving large S3 traffic off NAT onto a gateway endpoint can remove that NAT processing cost for that traffic.
- S3 Interface Endpoint. Billed per hour for each endpoint in each AZ, plus per-GB data processing. The AWS PrivateLink pricing page shows example rates for US East (N. Virginia) of $0.01 per hour per endpoint ENI and $0.01 per GB for the first tier. Rates are Region-specific, so verify them for your own Region.
- Placement matters. If you enable private DNS for all traffic on the interface endpoint, in-VPC traffic goes through it and incurs its data charges. The inbound-resolver-only mode lets VPC traffic use the free gateway endpoint instead.
- Other charges still apply. S3 requests, storage, and some data transfer scenarios (such as cross-Region or cross-AZ traffic) are billed separately.
I have not run cost benchmarks for this article, so I am not claiming any specific saving. Estimate with the AWS Pricing Calculator using your own traffic.
Common mistakes and troubleshooting
| Problem | What to check |
|---|---|
| Route table not associated | In the endpoint details, confirm the private route tables are listed. Check the subnet's actual route table. |
| No S3 prefix-list route | In the route table, look for a route with an S3 prefix list destination and a vpce- target. |
| IAM or bucket policy denies | Check the instance role, bucket policy and endpoint policy. Read the exact AccessDenied message and CloudTrail event. |
| Security group blocks HTTPS | For interface endpoints, allow 443 inbound from the client. Check the client's outbound rules and NACLs. |
| Wrong DNS | Run nslookup or dig for the S3 name from the failing host. Private IP means the interface endpoint, public IP means the gateway path or internet. Verify VPC DNS hostnames and resolution, and any on-premises forwarders. |
| Expecting the gateway endpoint to work from peered VPCs or via Transit Gateway | It does not extend out of its VPC. Create an endpoint in that VPC, or use an interface endpoint. |
| Expecting the interface endpoint to open S3 to every connected network | You still need routing to the ENI IPs, DNS that returns them, and security group access. |
| Subnet or AZ coverage gaps | Confirm the interface endpoint has a subnet in each AZ that needs it, and that the NAT and route tables match your AZ layout. |
| Bucket in a different Region | A gateway endpoint serves buckets in its own Region. Traffic to other Regions will take another path, such as NAT, and can cost more. |
| Wrong credentials or a restrictive bucket policy during tests | Confirm which identity the CLI uses with aws sts get-caller-identity. Remember that a aws:sourceVpce deny also blocks the console. |
A good habit is to test in order: DNS, then route, then security group, then policies.
Conclusion
- NAT Gateway: use it for outbound access to the public internet, or for private NAT toward other private networks.
- S3 Gateway Endpoint: use it as the default for workloads inside one VPC that need S3. No additional endpoint charge, but it does not leave the VPC.
- S3 Interface Endpoint: use it when on-premises systems or other networks need private S3 access, and prefer the inbound-resolver-only DNS option so the VPC itself keeps using the gateway endpoint.
Decision rule: gateway inside, interface outside, NAT for the open internet.
What does your S3 and NAT setup look like today? Have you found NAT charges you did not expect, or run a hybrid design with interface endpoints? Share it in the comments, I would love to compare patterns.
References
- Gateway endpoints for Amazon S3 (Amazon VPC)
- NAT gateways (Amazon VPC User Guide)
- Regional NAT gateways (Amazon VPC User Guide)
- AWS PrivateLink for Amazon S3 (S3 User Guide)
- Secure hybrid access to Amazon S3 using AWS PrivateLink (AWS Networking blog)
- Introducing private DNS support for Amazon S3 with AWS PrivateLink (AWS Storage blog)
- AWS PrivateLink pricing
- Amazon VPC pricing (NAT Gateway)
Top comments (0)