Skip to content
ExponaBLOG
AWS FOR DEVELOPERS · DAY 012 / 100

VPC endpoints: a complete AWS guide for production teams

Private access to AWS services — and lower NAT bills.

Expona Admin6 min read

If you're running applications in a VPC and accessing AWS services like S3 or DynamoDB, you're probably routing that traffic through a NAT gateway—and paying for every gigabyte that crosses it. VPC endpoints change that equation entirely. They let your EC2 instances, Lambda functions, and containers reach AWS services privately, without leaving your VPC, and without the data transfer costs that drain your bill every month.

Over the past year, I've seen teams save thousands in NAT charges just by switching S3 traffic to a gateway endpoint. That's the kind of win that makes this day's topic worth your attention.

Gateway endpoints — S3 and DynamoDB, free of charge

Gateway endpoints are the simplest and most cost-effective way to keep traffic inside your VPC. They're available for two services: S3 and DynamoDB. When you create a gateway endpoint, AWS injects routes into your route table so that traffic destined for those services never leaves your VPC—it stays within AWS infrastructure.

Here's the immediate benefit: no data transfer charges. Normally, traffic from your EC2 instance to S3 goes through your NAT gateway, and you pay $0.045 per GB (US East 1 pricing). With a gateway endpoint, that traffic is free. If your application is uploading or downloading terabytes of data monthly, this compounds quickly.

Setting up a gateway endpoint takes minutes. You specify which VPC and route tables should use it, then AWS handles the rest:

bash
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-12345678 \
  --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids rtb-abcd1234 rtb-efgh5678

Once created, any EC2 instance in those route tables can reach S3 using standard SDKs—no code changes needed. Internally, AWS modifies the route table so that traffic to S3 IP ranges gets intercepted and routed through the endpoint.

A real scenario: I worked on a data pipeline that ingested logs into S3 every minute from 20 EC2 instances across multiple availability zones. Before the gateway endpoint, we were hemorrhaging roughly $8,000 per month on NAT charges for that traffic alone. After adding the endpoint, we zeroed that cost out while keeping the application logic unchanged. The compute still ran in the same subnets; only the routing changed.

DynamoDB endpoints work the same way. If you're doing high-volume scans or batch writes from your application tier to DynamoDB, a gateway endpoint keeps that internal and saves money.

Gateway endpoints are limited to S3 and DynamoDB. Everything else—Secrets Manager, SNS, SQS, RDS, CloudWatch, APIs you've built yourself—uses interface endpoints instead.

Interface endpoints use AWS PrivateLink technology under the hood. Essentially, AWS provisions elastic network interfaces (ENIs) in your VPC, and those ENIs connect to the target service via AWS's private backbone network. You communicate with those ENIs as if they were the service itself.

When you create an interface endpoint, you specify a VPC and subnets. AWS spins up an ENI in each subnet you choose (for high availability across AZs). That ENI gets a private IP address, and you can optionally enable DNS so that service hostnames resolve to those private IPs:

bash
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-12345678 \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.secretsmanager \
  --subnet-ids subnet-aaaaa subnet-bbbbb \
  --security-group-ids sg-12345678 \
  --private-dns-enabled

With --private-dns-enabled, your application code doesn't change. When you call secretsmanager.get_secret_value(), the boto3 client resolves the hostname to your endpoint's private IP, and traffic stays inside your VPC.

I've used interface endpoints for Secrets Manager, SNS, and SQS in production. One pattern I recommend: create interface endpoints for services your application actually calls frequently. Don't over-engineer by creating endpoints for services you barely use. Each endpoint carries a small hourly charge ($0.01 per hour in most regions as of my last check), so the cost is low but not zero.

A practical example: imagine a microservice that needs to retrieve database credentials from Secrets Manager every time it starts. Without an interface endpoint, that request traverses your NAT gateway and leaves your VPC. With an endpoint, the request stays private. More importantly, if you've restricted your security group so that EC2 instances can only reach specific endpoints, you've now created a control plane that's impossible to bypass—you can't accidentally call an unauthorized service.

Why it matters — Traffic to S3 through NAT costs per GB

The financial argument for VPC endpoints is straightforward. Let me show you the math.

Suppose your application uploads 100 GB to S3 daily. That's roughly 3 TB monthly. Without a gateway endpoint, all that traffic routes through a NAT gateway:

  • NAT gateway data processing: $0.045 per GB (us-east-1)
  • 3 TB × 1024 GB/TB × $0.045 = $138.24 per month
  • Annual cost: $1,658.88

With a gateway endpoint, that cost drops to zero. No hourly charges, no per-request fees, no per-GB costs. You save $1,658.88 annually per TB of S3 traffic. At scale, this adds up to five or six figures.

Even smaller workloads benefit. I've seen teams save $500-$1,000 monthly just by adding gateway endpoints for S3, which pays back the time investment in about an hour.

The secondary benefit is architectural. By keeping traffic internal, you reduce latency. S3 requests no longer depend on NAT gateway capacity. Your S3 operations aren't competing with other outbound traffic for bandwidth. I've seen consistent 10-20ms latency improvements from EC2 to S3 when switching to gateway endpoints, which compounds across thousands of requests.

Security bonus — Endpoint policies limit which resources can be reached

Endpoints aren't just a cost optimization; they're a security boundary. You can attach resource-based policies to endpoints that restrict which S3 buckets, DynamoDB tables, or Secrets Manager secrets can be reached through that endpoint.

Here's a policy that restricts gateway endpoint access to a single S3 bucket:

bash
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Principal": "*",
      "Effect": "Allow",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-app-bucket/*"
    },
    {
      "Principal": "*",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::my-app-bucket"
    }
  ]
}

Apply this to your S3 gateway endpoint, and even if an EC2 instance is compromised and an attacker gains shell access, they can only reach my-app-bucket. They can't enumerate other buckets or access data belonging to other teams or applications.

For interface endpoints, combine the endpoint policy with security group rules. Your Secrets Manager interface endpoint's security group should only allow inbound traffic on port 443 from specific application security groups. If a rogue process tries to fetch a secret, it will be blocked at the network layer before reaching the endpoint.

This layered approach—VPC routing, security groups, and resource policies—creates defense in depth. I've used this pattern to enforce zero-trust networking at scale. Your applications can only reach the services they're explicitly allowed to reach, and there's no way to leak data through a public route.

Common mistakes

  • Creating gateway endpoints without updating route tables: If you create a gateway endpoint but don't associate it with the right route tables, traffic won't use it. Check your route table explicitly after creation to see the injected routes.
  • Forgetting to enable private DNS for interface endpoints: Without --private-dns-enabled, your code still works, but you have to hardcode the endpoint URL. Enable it and your code is portable across environments.
  • Overly permissive endpoint policies: A policy that allows "Action": "*" on all resources defeats the purpose. Write least-privilege policies that allow only what your application needs.
  • Creating interface endpoints you don't use: Each interface endpoint costs $0.01/hour. Don't create one for Kinesis if your application never calls it. Be intentional.
  • Ignoring NAT gateway costs after adding endpoints: Gateway endpoints only reduce S3 and DynamoDB traffic. Your other services still use NAT. Monitor CloudWatch metrics to ensure your NAT gateway is actually handling less traffic after you deploy endpoints.

Key takeaways

  • Gateway endpoints for S3 and DynamoDB are free, require no code changes, and typically save thousands annually by eliminating NAT charges.
  • Interface endpoints extend private access to most other AWS services and enforce a security boundary through policies and security groups.
  • Attach resource-based policies to endpoints to restrict which buckets, tables, or secrets can be reached, enforcing least-privilege access at the network layer.
  • Enable private DNS on interface endpoints so your application code doesn't need to know about the endpoint's existence.
  • Monitor NAT gateway metrics before and after deploying endpoints to quantify the savings and ensure they're working as expected.
  • Start with S3 and DynamoDB if those are your high-traffic services, then add interface endpoints for security-sensitive operations like fetching secrets.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles