Server

VPC Endpoints and PrivateLink

By Prabath Thalangama· October 24, 2026· 4 min read
#aws#vpc#networking

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.

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-1 from an eu-west-1 VPC with only an eu-west-1 S3 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), and logs — 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.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.