VPC Endpoints and PrivateLink
Introduction¶
By default, an instance in a private subnet reaches s3.amazonaws.com or
sqs.<region>.amazonaws.com via a NAT Gateway — over the public internet
(encrypted, but out and back). VPC endpoints provide a private path: traffic
stays on the AWS network, no NAT, often cheaper, and you can restrict it.
Two kinds¶
Gateway endpoints — S3 and DynamoDB only¶
A route table entry. Free. You add a prefix-list route
(pl-xxxx for com.amazonaws.<region>.s3) pointing at the endpoint; traffic to
S3 CIDRs goes there instead of the NAT.
# Route table for the private subnet gains:
Destination: pl-68a54001 (S3) Target: vpce-0abc...
No ENI, no DNS change (S3's public hostnames just route privately for subnets associated with the endpoint).
Interface endpoints — everything else¶
An ENI with a private IP in your subnet(s), powered by PrivateLink. Works for SQS, SNS, KMS, Secrets Manager, ECR, CloudWatch, STS, SSM, and ~most services. Costs per-hour per-AZ + per-GB.
aws ec2 create-vpc-endpoint \
--vpc-endpoint-type Interface \
--vpc-id vpc-0abc \
--service-name com.amazonaws.eu-west-1.secretsmanager \
--subnet-ids subnet-a subnet-b \
--security-group-ids sg-endpoints \
--private-dns-enabled
The endpoint's security group must allow inbound 443 from your instances' security groups.
Private DNS — the important toggle¶
With private-dns-enabled: true, the service's normal public hostname
(secretsmanager.eu-west-1.amazonaws.com) resolves to the endpoint's private
IP inside the VPC. Your code and SDKs need no changes — they call the same
endpoint, it just resolves privately.
Requires enableDnsSupport and enableDnsHostnames on the VPC. Without private
DNS, you'd have to use the ugly endpoint-specific hostname
(vpce-0abc-xyz.secretsmanager.eu-west-1.vpce.amazonaws.com).
Endpoint policies¶
An interface or gateway endpoint can carry a resource policy that restricts what can be done through it:
{
"Statement": [{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": ["arn:aws:s3:::my-bucket/*"]
}]
}
Now traffic through this endpoint can only touch my-bucket — a strong
data-exfiltration control. Combine with an S3 bucket policy condition
aws:sourceVpce to require access via the endpoint.
PrivateLink for your own services¶
Expose a service running behind a Network Load Balancer as an "endpoint service"; other VPCs (yours or a customer's) create an interface endpoint to it. Traffic never traverses the internet, no VPC peering, no CIDR overlap concerns, one-directional (consumer → provider).
Used for: SaaS vendors offering private connectivity, sharing an internal service across accounts/VPCs without peering.
Cost trade-off¶
- NAT Gateway: ~$0.045/hr + ~$0.045/GB processed.
- Interface endpoint: ~$0.01/hr per AZ + ~$0.01/GB.
- Gateway endpoint (S3/DynamoDB): free.
For heavy S3/DynamoDB traffic, gateway endpoints save real money and remove a dependency. For other services, do the math — a few low-traffic interface endpoints can cost more than the NAT they'd replace; many high-traffic ones save.
Verification and troubleshooting¶
aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=vpc-0abc
# From an instance:
nslookup secretsmanager.eu-west-1.amazonaws.com # should resolve to a 10.x / private IP
aws sts get-caller-identity # works without NAT?
- SDK still uses NAT / fails without NAT — private DNS not enabled on the
endpoint, or the VPC lacks
enableDnsHostnames/enableDnsSupport, or the service isn't covered (check the endpoint exists for it). could not connect to endpoint/ timeout — the endpoint's security group doesn't allow 443 from the instance SG, or the endpoint isn't in the same AZ as the instance (interface endpoints are per-subnet — add all AZs).- S3 access denied only through the endpoint — the endpoint policy is more restrictive than the IAM policy (they intersect). Loosen the endpoint policy or scope it correctly.
- Cross-region calls fail — endpoints are regional; a call to
s3.us-east-1from aneu-west-1VPC with only aneu-west-1S3 gateway endpoint still needs internet egress. - ECR pulls fail in a private subnet — you need
ecr.api,ecr.dkr, an S3 gateway endpoint (image layers), andlogs— a common four-endpoint set for Fargate/EKS without NAT. - Higher bill after adding endpoints — many low-traffic interface endpoints; consolidate or drop the ones that don't pay for themselves vs the NAT.
Related tools and reading¶
- On-site: Subnet Calculator, DNS lookup.
- Related posts: VPC subnets and route tables, Security groups vs NACLs.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Cloud & AWS
AWS WAF: The Rules That Matter
AWS WAF filters HTTP requests at the ALB/CloudFront/API Gateway edge. Here's a sensible baseline Web ACL, how to tune with count mode, and the false-positive traps.
October 24, 2026An S3 Static Site Behind CloudFront
The right way to host a static site on AWS: a private S3 bucket, CloudFront with OAC in front, HTTPS via ACM, and SPA fallback routing. Plus the 403s that trip everyone.
October 24, 2026CloudWatch Logs and Metric Filters
CloudWatch Logs is cheap to write and expensive to forget about. Here's retention, turning error patterns into metrics and alarms, and Logs Insights for ad-hoc queries.
October 24, 2026