AWS for DevOps — VPC Peering & Endpoints

3 minute read

4.5 — VPC Peering & Endpoints

VPC Peering connects two VPCs so they can communicate using private IPs. VPC Endpoints let your private subnets access AWS services (S3, DynamoDB, ECR) without going through the internet.

VPC Peering

  VPC-A (10.0.0.0/16)              VPC-B (10.1.0.0/16)
  ┌──────────────────┐             ┌──────────────────┐
  │  App Servers      │◄──Peering──►│  Shared Services  │
  │  10.0.10.0/24     │             │  10.1.10.0/24     │
  └──────────────────┘             └──────────────────┘
  • CIDRs must not overlap
  • Traffic stays on AWS private network (never crosses the internet)
  • Works across accounts and across regions
  • Not transitive — if A peers with B and B peers with C, A cannot reach C through B

Creating a VPC Peering Connection

# Request peering (from VPC-A's account)
$ aws ec2 create-vpc-peering-connection \
    --vpc-id vpc-a123 \
    --peer-vpc-id vpc-b456 \
    --peer-region ap-south-1
{
    "VpcPeeringConnection": {
        "VpcPeeringConnectionId": "pcx-abc123",
        "Status": {"Code": "initiating-request"}
    }
}

# Accept peering (from VPC-B's account, or same account)
$ aws ec2 accept-vpc-peering-connection \
    --vpc-peering-connection-id pcx-abc123

# Add routes in BOTH VPCs
# VPC-A route table: send 10.1.0.0/16 traffic to the peering connection
$ aws ec2 create-route \
    --route-table-id rtb-vpca \
    --destination-cidr-block 10.1.0.0/16 \
    --vpc-peering-connection-id pcx-abc123

# VPC-B route table: send 10.0.0.0/16 traffic to the peering connection
$ aws ec2 create-route \
    --route-table-id rtb-vpcb \
    --destination-cidr-block 10.0.0.0/16 \
    --vpc-peering-connection-id pcx-abc123

Don’t forget: Update security groups in both VPCs to allow traffic from the peer CIDR.

VPC Endpoints

Without endpoints, an EC2 instance in a private subnet talks to S3 like this:

  Private Subnet → NAT Gateway → IGW → Internet → S3

With a VPC Endpoint:

  Private Subnet → VPC Endpoint → S3 (stays on AWS network)

Benefits: No NAT costs, lower latency, more secure (traffic never leaves AWS).

Two Types of VPC Endpoints

Type How It Works Services
Gateway Endpoint Route table entry S3, DynamoDB
Interface Endpoint ENI with private IP (powered by PrivateLink) Almost everything else (ECR, CloudWatch, STS, SSM, etc.)

Gateway Endpoint (for S3)

$ aws ec2 create-vpc-endpoint \
    --vpc-id vpc-0abc123 \
    --service-name com.amazonaws.ap-south-1.s3 \
    --route-table-ids rtb-prv123 rtb-prv456
{
    "VpcEndpoint": {
        "VpcEndpointId": "vpce-s3abc",
        "ServiceName": "com.amazonaws.ap-south-1.s3",
        "VpcEndpointType": "Gateway"
    }
}

AWS automatically adds a route in the specified route tables:

  Destination: pl-abc123 (S3 prefix list)
  Target: vpce-s3abc

Gateway endpoints are free. Always create them for S3 and DynamoDB.

Interface Endpoint (for ECR, CloudWatch, etc.)

# Create an interface endpoint for ECR (Docker pull)
$ aws ec2 create-vpc-endpoint \
    --vpc-id vpc-0abc123 \
    --service-name com.amazonaws.ap-south-1.ecr.dkr \
    --vpc-endpoint-type Interface \
    --subnet-ids subnet-prv1a subnet-prv1b \
    --security-group-ids sg-endpoint123 \
    --private-dns-enabled
{
    "VpcEndpoint": {
        "VpcEndpointId": "vpce-ecr123",
        "VpcEndpointType": "Interface",
        "DnsEntries": [
            {"DnsName": "vpce-ecr123.ecr.dkr.ap-south-1.vpce.amazonaws.com"}
        ]
    }
}

With --private-dns-enabled, the standard ECR DNS name (*.ecr.ap-south-1.amazonaws.com) resolves to the endpoint’s private IP. No code changes needed.

Endpoints Needed for ECS Fargate in a Private Subnet

ECS Fargate tasks in private subnets need these endpoints (or a NAT Gateway):

Endpoint Service Name Why
ECR (API) com.amazonaws.REGION.ecr.api Pull image metadata
ECR (Docker) com.amazonaws.REGION.ecr.dkr Pull image layers
S3 (Gateway) com.amazonaws.REGION.s3 ECR stores layers in S3
CloudWatch Logs com.amazonaws.REGION.logs Write container logs
SSM com.amazonaws.REGION.ssm ECS Exec

Endpoint Policies

You can restrict what a VPC endpoint allows:

{
  "Statement": [
    {
      "Sid": "AllowSpecificBucket",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::my-deploy-bucket/*"
    }
  ]
}

This ensures that even if an instance has broad S3 permissions, it can only access my-deploy-bucket through this endpoint.

Common Mistakes

Mistake Why It Hurts Fix
Overlapping CIDRs between peered VPCs Peering request rejected Plan CIDR allocation before creating VPCs
Assuming peering is transitive Traffic between A→C doesn’t flow through B Create direct peering or use Transit Gateway
Not creating S3 gateway endpoint All S3 traffic goes through NAT (costs money) Always create free gateway endpoints
Forgetting security group for interface endpoint Endpoint rejects connections Allow port 443 from your subnets

When to Use Which

Scenario Solution
Connect 2 VPCs VPC Peering
Connect 10+ VPCs (hub-spoke) Transit Gateway
S3 / DynamoDB from private subnet Gateway Endpoint (free)
ECR / CloudWatch / SSM from private subnet Interface Endpoint
Avoid NAT Gateway costs for AWS services VPC Endpoints

Quick Reference

Task Command
Create peering aws ec2 create-vpc-peering-connection --vpc-id VPC_A --peer-vpc-id VPC_B
Accept peering aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id PCX
Create gateway endpoint aws ec2 create-vpc-endpoint --vpc-id VPC --service-name SVC --route-table-ids RT
Create interface endpoint aws ec2 create-vpc-endpoint --vpc-id VPC --service-name SVC --vpc-endpoint-type Interface --subnet-ids SUBNET --security-group-ids SG
List endpoints aws ec2 describe-vpc-endpoints --filters Name=vpc-id,Values=VPC

Next → Networking Exercises

Updated: