AWS VPC Endpoints Step by Step - Gateway Endpoints for S3 and DynamoDB, Interface Endpoints (PrivateLink) for SSM, ECR and Other Services, Private DNS, Endpoint Policies, Security Groups, Cost vs NAT Gateway, and Troubleshooting (AWS Part-19)


In Part-14 I kept saying "add the S3 gateway endpoint and that traffic stops costing NAT money". This Part-19 is that feature in full. A VPC endpoint lets instances in a private subnet reach AWS services - S3, DynamoDB, Systems Manager, ECR, CloudWatch Logs, Secrets Manager, two hundred more - without an Internet Gateway, a NAT Gateway or a public IP. The traffic stays inside the AWS network, the private subnet can be truly private, and the per-GB NAT charge disappears.

We create the two kinds that matter - a gateway endpoint for S3 and interface endpoints for Systems Manager - test both from an instance that has no route to the internet at all, and then go through private DNS, policies, the ECR special case, cost and errors. Checked against the current PrivateLink documentation, which has grown two new endpoint types since the video (resource and service-network endpoints).

Table of Content

  1. Why VPC endpoints - the problem with NAT for AWS traffic
  2. The endpoint types - gateway, interface, and the newer ones
  3. Prerequisites - a private instance with no internet
  4. Step 1 - Gateway endpoint for S3
  5. Step 2 - Interface endpoints for Systems Manager (Session Manager without internet)
  6. Private DNS - how it stays transparent
  7. Endpoint policies and bucket policies with aws:sourceVpce
  8. The ECR, CloudWatch and Secrets Manager endpoint sets
  9. Interface endpoints from peered VPCs, Transit Gateway and on-premises
  10. What it costs - endpoint vs NAT Gateway
  11. The AWS CLI equivalents
  12. Quotas
  13. Common VPC endpoint errors and how to fix them
  14. Conclusion



1. Why VPC endpoints - the problem with NAT for AWS traffic

An instance in a private subnet calling aws s3 cp resolves s3.eu-central-1.amazonaws.com to a public IP and sends the request out through the NAT Gateway and the Internet Gateway to S3's public endpoint - and back. Three things are wrong with that -

  1. Cost - every byte pays the NAT data-processing fee ($0.045 per GB); backups, logs and container images add up fast.
  2. Exposure - the subnet needs an outbound path to the internet at all, which auditors dislike and which malware uses for exfiltration.
  3. Dependency - no NAT Gateway, no S3; no Internet Gateway, no Session Manager.

A VPC endpoint gives the VPC a private path to the service. The instance resolves the same hostname, but to a private address inside the VPC (interface endpoint) or routes the service's IP ranges through a special target (gateway endpoint). No public IP, no NAT, no internet.

VPC endpoints - gateway endpoint for S3 via the route table, interface endpoint with private DNS for SSM, compared with the NAT path


2. The endpoint types - gateway, interface, and the newer ones

From the PrivateLink concepts -

TypeForHow it worksCost
Gateway endpointS3 and DynamoDB onlya target in your route tables for the service's prefix list; no network interfacefree
Interface endpoint (PrivateLink)200+ AWS services, Marketplace SaaS, your own services behind an NLBan elastic network interface with a private IP in each subnet you choose; DNS resolves the service name to itper AZ-hour + per GB
Gateway Load Balancer endpointinserting firewalls/appliances in the traffic patha route-table target that hands packets to a GWLB fleetper hour + per GB
Resource endpointone shared resource - an RDS database, an EC2 instance, an IP or DNS name in another VPC/account - without a load balancerENI, via a provider-side resource gateway and RAM sharingper hour + per GB
Service-network endpointmany resources and services on a VPC Lattice service network through one endpointENIper hour + per GB

Gateway endpoints are older, free, and limited to the two services; interface endpoints are the general mechanism (and S3 also has an interface endpoint for the cases a gateway cannot serve - section 9). The rest of this post builds one of each of the first two.



3. Prerequisites - a private instance with no internet

From Part-5: jhooq-vpc with private subnet jhooq-private-1a and an instance jhooq-app in it with an IAM instance profile that allows S3 read and includes AmazonSSMManagedInstanceCore (Part-3). To make the test honest, remove the NAT route: edit jhooq-private-rt and delete 0.0.0.0/0 → nat-... (or use a VPC with no NAT at all). Confirm from the bastion → app server -

1aws s3 ls s3://jhooq-demo-bucket/ --region eu-central-1
2# ... hangs, then: Connect timeout on endpoint URL: "https://jhooq-demo-bucket.s3.eu-central-1.amazonaws.com/"

No internet, no S3. Now we fix that without giving it internet.


4. Step 1 - Gateway endpoint for S3

VPC → Endpoints → Create endpoint -

  1. Name tag jhooq-s3-gw. Type - AWS services.
  2. Services - search s3, pick com.amazonaws.eu-central-1.s3 with Type Gateway (the one with type Interface is the other kind).
  3. VPC - jhooq-vpc.
  4. Route tables - tick jhooq-private-rt (and any other table whose subnets should use it). This is the whole mechanism: AWS adds a route whose destination is the S3 prefix list pl-6ea54007 (all S3 IP ranges in the region) and whose target is the endpoint vpce-....
  5. Policy - Full access for now (section 7 restricts it).
  6. Create endpoint. State Available within a minute.

Open jhooq-private-rt → Routes - a new row pl-6ea54007 (com.amazonaws.eu-central-1.s3) → vpce-0abc... appeared. Back on the instance -

1aws s3 ls s3://jhooq-demo-bucket/ --region eu-central-1
2# 2026-10-10 10:00:00      1234 report.pdf
3aws s3 cp s3://jhooq-demo-bucket/report.pdf /tmp/

Works, with no NAT and no public IP. The same for DynamoDB: com.amazonaws.eu-central-1.dynamodb, type Gateway. Two things to know - the gateway endpoint only works from inside the VPC (not over peering, VPN or Transit Gateway), and the instance must use the regional S3 endpoint (the CLI does; very old SDKs pointed at the global s3.amazonaws.com, which the gateway in other regions does not cover).



5. Step 2 - Interface endpoints for Systems Manager (Session Manager without internet)

Session Manager (Part-4) needs the SSM agent on the instance to reach three services. With no internet it cannot, so the instance shows as not managed. Three interface endpoints fix it. First a security group for the endpoints - jhooq-vpce-sg in jhooq-vpc, inbound HTTPS 443 from 10.0.0.0/16 (the endpoint ENIs receive the traffic, so they need to allow the callers).

Create endpoint (interface endpoints), three times -

  1. Name tag jhooq-ssm. Type AWS services. Services - com.amazonaws.eu-central-1.ssm (type Interface).
  2. VPC jhooq-vpc. Additional settings → Enable DNS name - keep ticked (private DNS, section 6).
  3. Subnets - tick one subnet per AZ where you have instances - jhooq-private-1a, jhooq-private-1b. One ENI (and one AZ-hour charge) per subnet.
  4. IP address type IPv4. Security groups - jhooq-vpce-sg. Policy - Full access.
  5. Create endpoint. Repeat for com.amazonaws.eu-central-1.ssmmessages and com.amazonaws.eu-central-1.ec2messages.

After a couple of minutes all three are Available. On the instance restart the agent (sudo systemctl restart snap.amazon-ssm-agent.amazon-ssm-agent on Ubuntu, amazon-ssm-agent on Amazon Linux) and within a minute Systems Manager → Fleet Manager lists it as Online. EC2 → the instance → Connect → Session Manager opens a shell - into a subnet that has no route to the internet whatsoever. That is the production pattern: private subnets, no bastion, no port 22, shell access logged by SSM.

Under EC2 → Network interfaces you can see the endpoint ENIs (VPC Endpoint Interface vpce-...), each with a private IP from your subnet - that is what the service name now resolves to.


6. Private DNS - how it stays transparent

With Enable DNS name (private DNS) on, Route 53 creates a hidden private hosted zone for ssm.eu-central-1.amazonaws.com associated with the VPC, so -

1# inside the VPC
2dig +short ssm.eu-central-1.amazonaws.com
3# 10.0.11.200
4# 10.0.12.200          <- the endpoint ENIs
5
6# from your laptop
7dig +short ssm.eu-central-1.amazonaws.com
8# 52.94.x.y            <- the public endpoint

Nothing in the CLI, SDK or agent configuration changes - the same hostname just resolves differently inside the VPC. Requirements: the VPC has DNS hostnames and DNS resolution enabled (Part-5, Step 1), and instances use the VPC resolver. With private DNS off, the endpoint only answers on its endpoint-specific DNS name (vpce-0abc-xyz.ssm.eu-central-1.vpce.amazonaws.com) and you must point clients at it with --endpoint-url - occasionally wanted, usually not.

The S3 interface endpoint is the exception - its private DNS is off by default (gateway endpoints already serve in-VPC traffic), and it supports inbound Resolver endpoint style private DNS for on-premises if you turn it on.



7. Endpoint policies and bucket policies with aws:sourceVpce

Two policies control what flows through an endpoint (control access) -

Endpoint policy - an IAM resource policy on the endpoint, default "allow everything for everyone". For the S3 gateway endpoint, restrict to your buckets so that a compromised instance cannot upload to an attacker's bucket through your own endpoint -

1{
2  "Version": "2012-10-17",
3  "Statement": [{
4    "Effect": "Allow",
5    "Principal": "*",
6    "Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket"],
7    "Resource": ["arn:aws:s3:::jhooq-demo-bucket", "arn:aws:s3:::jhooq-demo-bucket/*"]
8  }]
9}

Careful with ECR and Amazon Linux package repositories, which live in AWS-owned S3 buckets - the ECR docs list the bucket ARNs (arn:aws:s3:::prod-eu-central-1-starport-layer-bucket/*) you must allow, and amazonlinux.*.amazonaws.com repos need theirs too.

Resource policy on the other side - a bucket policy that only allows access through the endpoint, so the bucket is unreachable from the internet even with valid credentials -

 1{
 2  "Version": "2012-10-17",
 3  "Statement": [{
 4    "Sid": "OnlyViaOurEndpoint",
 5    "Effect": "Deny",
 6    "Principal": "*",
 7    "Action": "s3:*",
 8    "Resource": ["arn:aws:s3:::jhooq-demo-bucket", "arn:aws:s3:::jhooq-demo-bucket/*"],
 9    "Condition": { "StringNotEquals": { "aws:sourceVpce": "vpce-0abc1234567890def" } }
10  }]
11}

aws:sourceVpce and aws:sourceVpc condition keys work in bucket policies, IAM policies and SCPs (Part-2). Do test the deny from your laptop before you rely on it - and remember that the console itself does not go through the endpoint, so that policy locks the console out of the bucket too, unless you add an exception for your admin role. Effective permission is the intersection of the IAM policy, the endpoint policy and the bucket policy.


8. The ECR, CloudWatch and Secrets Manager endpoint sets

Some services need several endpoints to work privately - the sets people search for -

GoalEndpoints
Session Manager / SSMssm, ssmmessages, ec2messages (interface) + logs if you ship session logs
Pull images from ECR (ECS, EKS, EC2 Docker)ecr.api, ecr.dkr (interface) + the S3 gateway endpoint - image layers are stored in S3
CloudWatch Logs and metricslogs, monitoring
Secrets and parameterssecretsmanager, ssm (Parameter Store is part of SSM), kms for decryption
STS for role assumptionsts (regional STS endpoint - set AWS_STS_REGIONAL_ENDPOINTS=regional on older SDKs)
Lambda in a VPC calling AWSthe endpoints of whatever it calls - dynamodb (gateway), sqs, sns, lambda
Private API Gatewayexecute-api
EKS worker nodes with no NATecr.api, ecr.dkr, S3 gateway, ec2, sts, logs, elasticloadbalancing, autoscaling (+ eks for the API)

The full list of services and their names is AWS services that integrate with PrivateLink. Missing one of a set is the classic cause of "it works with NAT, breaks without" (section 13).


9. Interface endpoints from peered VPCs, Transit Gateway and on-premises

A gateway endpoint is only usable from inside its VPC. An interface endpoint has an ENI with a private IP, so anything that can route to that IP can use it - a peered VPC (Part-12), spokes behind a Transit Gateway (Part-13), and on-premises over VPN or Direct Connect. That is the centralised endpoints pattern: one shared-services VPC holds the interface endpoints (and pays the AZ-hours once), every spoke VPC routes to it, and DNS is handled by associating the endpoints' private hosted zones with the spoke VPCs or by a Route 53 Resolver inbound endpoint for on-premises. For S3 specifically, that is why the S3 interface endpoint exists next to the gateway one - the gateway cannot be shared, the interface can.



10. What it costs - endpoint vs NAT Gateway

From the PrivateLink pricing page -

Gateway endpointInterface endpointNAT Gateway
Hourlyfree$0.01 per AZ per hour (about $7.30 a month per AZ)$0.045 per hour (about $33 a month)
Datafree$0.01 per GB (first PB; cheaper beyond)$0.045 per GB
Also--public IPv4 address, data transfer out

So the S3 and DynamoDB gateway endpoints are a pure win - create them in every VPC. Interface endpoints pay off when the traffic to that service is large (1 TB of ECR pulls through NAT = $46; through endpoints = $10 + hours) or when the requirement is "no internet path at all". Three SSM endpoints in two AZs cost about $44 a month in hours - about the same as the NAT Gateway they can replace for a fully private VPC, with none of its data charges. For a lab, delete the interface endpoints when done; the gateway endpoint can stay.


11. The AWS CLI equivalents

 1# gateway endpoint for S3 on the private route table
 2aws ec2 create-vpc-endpoint --vpc-id vpc-0abc --vpc-endpoint-type Gateway \
 3  --service-name com.amazonaws.eu-central-1.s3 --route-table-ids rtb-private1a rtb-private1b \
 4  --tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=jhooq-s3-gw}]'
 5
 6# interface endpoints for SSM (one subnet per AZ, private DNS on)
 7for svc in ssm ssmmessages ec2messages; do
 8  aws ec2 create-vpc-endpoint --vpc-id vpc-0abc --vpc-endpoint-type Interface \
 9    --service-name "com.amazonaws.eu-central-1.$svc" \
10    --subnet-ids subnet-private1a subnet-private1b --security-group-ids sg-0vpce \
11    --private-dns-enabled --tag-specifications "ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=jhooq-$svc}]"
12done
13
14# endpoint policy on the S3 gateway
15aws ec2 modify-vpc-endpoint --vpc-endpoint-id vpce-0abc --policy-document file://s3-endpoint-policy.json
16
17# which services exist in this region?
18aws ec2 describe-vpc-endpoint-services --query 'ServiceNames' --output text | tr '\t' '\n' | grep -E 'ecr|logs'
19
20aws ec2 describe-vpc-endpoints --query 'VpcEndpoints[].[VpcEndpointId,ServiceName,VpcEndpointType,State]' --output table

Terraform: aws_vpc_endpoint with vpc_endpoint_type = "Gateway" + route_table_ids, or "Interface" + subnet_ids, security_group_ids, private_dns_enabled = true; aws_vpc_endpoint_policy for the policy.


12. Quotas

From the endpoint quotas - 50 interface endpoints per VPC (adjustable), 20 gateway endpoints per region, up to 10 Gbps per AZ per interface endpoint scaling to 100 Gbps, endpoint policies up to 20,480 characters, and gateway endpoints do not have a bandwidth limit.


13. Common VPC endpoint errors and how to fix them

1. Connect timeout on endpoint URL: "https://ssm.eu-central-1.amazonaws.com/" after creating the interface endpoint - The endpoint security group does not allow 443 from the instance's subnet/VPC CIDR, or private DNS is off so the name still resolves to the public IP (dig it from the instance - it must be a 10.x address). Also check the endpoint is in a subnet of the instance's AZ (cross-AZ works but needs a route and costs transfer).

2. S3 still times out with the gateway endpoint - The endpoint is not associated with the route table the instance's subnet uses (check Subnet associations), or the client targets the global/other-region S3 endpoint - set --region / the SDK region. A 0.0.0.0/0 route is not needed, but a more specific wrong route can hijack traffic.

3. AccessDenied through the endpoint although IAM allows it - The endpoint policy does not allow the action/bucket (default allows all; a restricted policy forgot a bucket), or the bucket policy with aws:sourceVpce names a different endpoint ID (one per VPC).

4. private DNS names ... can't be enabled / enableDnsHostnames must be true - Turn on DNS hostnames and DNS resolution on the VPC.

5. The VPC endpoint ... conflicts with ... private hosted zone - You already have a private hosted zone for the same service domain (from a manual setup). Delete it or create the endpoint without private DNS.

6. ECR pull fails: dial tcp ... i/o timeout or 403 after adding ecr endpoints - Missing one of the set - ecr.api and ecr.dkr and the S3 gateway endpoint (layers are in S3), and an endpoint policy that excludes the ECR S3 bucket.

7. SSM instance still "not managed" - One of the three SSM endpoints missing, the agent not restarted, no instance profile with AmazonSSMManagedInstanceCore, or the endpoint SG blocks 443.

8. VpcEndpointLimitExceeded - 50 interface endpoints per VPC - request an increase or centralise endpoints in a shared VPC (section 9).

9. Works for instances in 1a, not in 1b - Interface endpoints have an ENI only in the subnets you selected; add a subnet in 1b (or accept cross-AZ traffic via a route).

10. On-premises cannot resolve the endpoint names - Private DNS only answers inside the VPC. Add a Route 53 Resolver inbound endpoint and forward the *.amazonaws.com names to it, or use the endpoint-specific DNS names.

11. Gateway endpoint not usable from the peered VPC - By design. Use an interface endpoint (S3 has one) in the hub VPC.


14. Conclusion

To summarise Part-19 -

  1. A VPC endpoint gives private subnets a private path to AWS services - no NAT, no Internet Gateway, no public IP, no internet.
  2. Gateway endpoints (S3, DynamoDB) are a route-table target, free, in-VPC only - create them in every VPC on day one.
  3. Interface endpoints (PrivateLink) are ENIs with private IPs per subnet, reachable from peered VPCs, Transit Gateway and on-premises, with a security group that must allow 443 and private DNS that keeps the service hostname unchanged; they cost about $7 per AZ per month plus $0.01 per GB.
  4. Endpoint policies restrict what goes through; aws:sourceVpce in bucket and IAM policies restricts where requests may come from.
  5. Some services come in sets - SSM needs three endpoints, ECR needs two plus the S3 gateway - and a missing member is the usual "works with NAT, not without" cause.

The official references are the PrivateLink guide, PrivateLink concepts, create an interface endpoint, gateway endpoints and controlling access with endpoint policies. In the next part we flip to the provider side - publishing your own service through PrivateLink with an endpoint service and an NLB (Part-18 built the NLB for it).


AWS step by step series -

  1. Part-1 : AWS IAM user - create a user, group, policy, access keys and MFA
  2. Part-2 : AWS Organizations - multi-account setup, OUs and SCPs
  3. Part-3 : AWS assume IAM role - trust policy, switch role in console and CLI
  4. Part-4 : How to launch an EC2 instance - key pair, security group, SSH
  5. Part-5 : AWS VPC - public and private subnets, Internet Gateway, NAT Gateway, route tables
  6. Part-8 : EC2 launch template - versions, default version, source template, SSM parameter AMI
  7. Part-10 : EC2 Auto Scaling - launch template, Auto Scaling group, target tracking, ALB
  8. Part-11 : AWS WAF - web ACL, managed rules, rate limiting, geo blocking
  9. Part-12 : AWS VPC Peering - connect two VPCs, routes, security groups, DNS
  10. Part-13 : AWS Transit Gateway - hub-and-spoke for many VPCs and on-premises
  11. Part-14 : AWS NAT Gateway deep dive - public vs private, limits, cost, troubleshooting
  12. Part-15 : Amazon Route 53 - hosted zones, records, alias, routing policies, health checks
  13. Part-16 : AWS security groups - inbound and outbound rules, stateful, referencing, quotas
  14. Part-16 : AWS Certificate Manager - free TLS certificates for ALB, CloudFront and API Gateway
  15. Part-17 : AWS Lambda - function URLs, environment variables and layers
  16. Part-18 : Network Load Balancer - setup, and ALB vs NLB
  17. Part-19 : VPC endpoints - gateway and interface endpoints (PrivateLink) instead of NAT
  18. Part-20 : AWS PrivateLink - publish your own service with an endpoint service and NLB
  19. Part-20 : Amazon EBS volumes - types, attach, mount, resize, snapshots, encryption
  20. Part-21 : VPC Flow Logs - CloudWatch Logs, S3, record format, Logs Insights, Athena
  21. Part-21 : EC2 Spot Instances - pricing, interruptions, mixed instances groups
  22. Part-24 : AWS Control Tower - landing zone, controls, Account Factory, Identity Center

Networking fundamentals -

  1. What is a VPC and a subnet? AWS networking in five minutes
  2. What is CIDR? Calculate IP ranges for VPCs and subnets
  3. What is NAT? Static NAT, dynamic NAT and PAT explained

More AWS guides -

  1. What is AWS CloudFormation? Templates, stacks, change sets, drift, StackSets
  2. Learn AWS S3 - the complete course
  3. AWS API Gateway - REST API with Lambda, authorizers, Terraform
  4. AWS Advanced Networking Specialty (ANS-C01) - course companion
  5. AWS ECS and Fargate - how to deploy a Docker container
  6. AWS S3 - how to host a static website
  7. Terraform create EC2 instance on AWS
  8. Terraform AWS IAM - users, roles and policies
  9. Terraform and AWS multi-account setup
  10. Terraform - setting up an ALB and SSL

Posts in this series