VPC Endpoints and AWS PrivateLink
Concept
A resource in a private subnet normally reaches any AWS service (S3, DynamoDB, Secrets Manager) the same way it reaches the public internet: routed out through a NAT Gateway, over the internet, to a public AWS endpoint. A VPC endpoint replaces that path with a private connection that never leaves AWS's network — no NAT Gateway, no internet gateway, no public IP involved. AWS PrivateLink is the underlying service that makes this possible; a VPC endpoint is the object you create inside your VPC to actually use it.
There are two distinct kinds of VPC endpoint, and they aren't interchangeable:
- Gateway endpoints exist only for S3 and DynamoDB. They work by adding a route to the VPC's route table that sends traffic destined for that service directly to the endpoint instead of the internet gateway — no PrivateLink, no elastic network interface, and no additional charge. The tradeoff is reach: gateway endpoints don't work from on-premises networks, from peered VPCs in other regions, or through a Transit Gateway — they're scoped to the VPC whose route table has the entry.
- Interface endpoints work for a much broader set of AWS services (API Gateway, CloudWatch, Bedrock, Secrets Manager, and most others), plus PrivateLink-enabled third-party SaaS. They deploy an elastic network interface with a private IP directly into your subnet, so the service is reachable at a normal private IP address — which also means they're reachable from on-premises over Direct Connect or a Site-to-Site VPN, unlike gateway endpoints. Interface endpoints carry an hourly charge plus per-GB data processing cost.
Both can coexist for the same service: it's a documented pattern to keep an S3 gateway endpoint for in-VPC traffic (free, simple) while adding an S3 interface endpoint specifically for on-premises access that the gateway endpoint can't serve.
Tradeoffs
| Dimension | Gateway Endpoint | Interface Endpoint |
|---|---|---|
| Services supported | S3, DynamoDB only | Most AWS services + PrivateLink-enabled SaaS |
| Mechanism | Route table entry | ENI with private IP in your subnet |
| Cost | Free | Hourly + per-GB data processing charge |
| Reachable from on-premises / peered VPCs / Transit Gateway | No | Yes |
| DNS | Public service DNS names still resolve to it within the VPC | Private DNS optionally overrides public names |
When to use / when not to
- Use a VPC endpoint (either kind) whenever a private-subnet resource talks to an AWS service that has one available — it removes the NAT Gateway from that path entirely, which is both a security win (traffic never touches the public internet, smaller attack surface) and a cost win (NAT Gateway data-processing charges are often more expensive than a comparable interface endpoint's, and gateway endpoints are free).
- Default to a gateway endpoint for S3/DynamoDB traffic that originates inside the VPC — it's free and simpler. Reach for an interface endpoint instead when the traffic needs to originate on-premises, from a peered VPC, or through a Transit Gateway, since gateway endpoints can't serve those paths.
- This only works for AWS services (or specifically PrivateLink-enabled SaaS, which some vendors — including database-as-a-service providers — offer as a private connectivity option to their own managed infrastructure). An arbitrary third-party API with no PrivateLink offering has no endpoint to connect to; that traffic has no alternative to routing through NAT Gateway, no matter how much VPC-endpoint infrastructure exists elsewhere in the account.
- Don't default to provisioning an interface endpoint for every service "just in case" — each one is a standing hourly cost. Add them where the traffic volume or compliance requirement (avoiding public internet transit for sensitive calls) actually justifies it.
Common pitfall
Assuming any AWS-service-shaped traffic can be moved off NAT Gateway onto PrivateLink, without checking whether that specific service actually offers an endpoint, or confusing "AWS SDK call" with "AWS service" when the actual destination is a third-party API accessed via an SDK-style client. The fix is to check per-service, per-call: for each outbound destination, confirm whether it's an AWS service (or PrivateLink-enabled SaaS, e.g. many managed database providers offer PrivateLink connectivity to their hosted clusters) with a real VPC endpoint available before assuming it can bypass NAT Gateway — anything else keeps the NAT Gateway path as the only option, and treating a mixed workload as uniformly convertible leads to under-provisioned NAT Gateway capacity once the mistake surfaces under load.
Engineering Lens
The design-review-worthy question isn't "are we using PrivateLink" as a checkbox — it's a per-destination audit: for each AWS service and each SaaS dependency a private-subnet workload calls, is there a VPC endpoint available, and if not, is the NAT Gateway path's cost and public-internet exposure an accepted tradeoff or an unexamined default. A workload that calls five AWS services and one third-party API doesn't get to claim "we use VPC endpoints" as a blanket security posture if only two of the five actually have endpoints wired up — the other three (and the third-party API) are still transiting NAT Gateway and the public internet, and that gap needs to be named explicitly rather than assumed away by the presence of endpoints elsewhere in the account.