How VPC Peering Routes Traffic Privately
How AWS VPC peering connections route traffic between VPCs using the AWS backbone without IGW, NAT, or VPN.
Introduction
AWS Virtual Private Clouds (VPCs) are isolated network segments — by default, two VPCs cannot communicate with each other even within the same AWS account. VPC Peering creates a private network connection between two VPCs, allowing resources in each to communicate using their private IP addresses as if they were on the same network. Traffic traverses the AWS backbone network — never the public internet — without requiring an internet gateway, NAT gateway, VPN, or virtual private gateway.
Step-by-Step: Establishing VPC Peering
flowchart TD
A["1. Create the Peering Connection"]
B["2. Update Route Tables"]
C["3. Update Security Groups"]
D["4. Non-Transitive Routing (Key Limitation)"]
E["5. CIDR Overlap Prevention"]
A --> B
B --> C
C --> D
D --> E
Step 1: Create the Peering Connection
# Create peering request from VPC A to VPC B
aws ec2 create-vpc-peering-connection --vpc-id vpc-aaa111 # Requester VPC
--peer-vpc-id vpc-bbb222 # Accepter VPC
--peer-region us-west-2 # Cross-region peering (optional)
# VPC B owner must accept the request
aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id pcx-0abc123def456789
In Terraform:
resource "aws_vpc_peering_connection" "main" {
vpc_id = aws_vpc.vpc_a.id
peer_vpc_id = aws_vpc.vpc_b.id
auto_accept = true # Only works within same account
tags = { Name = "prod-to-data-peering" }
}
Step 2: Update Route Tables
Creating the peering connection is not enough — you must add routes in both VPCs’ route tables pointing to the peering connection:
# Route in VPC A's route table → VPC B's CIDR via peering
resource "aws_route" "vpc_a_to_vpc_b" {
route_table_id = aws_route_table.vpc_a_private.id
destination_cidr_block = "10.1.0.0/16" # VPC B CIDR
vpc_peering_connection_id = aws_vpc_peering_connection.main.id
}
# Route in VPC B's route table → VPC A's CIDR via peering
resource "aws_route" "vpc_b_to_vpc_a" {
route_table_id = aws_route_table.vpc_b_private.id
destination_cidr_block = "10.0.0.0/16" # VPC A CIDR
vpc_peering_connection_id = aws_vpc_peering_connection.main.id
}
Step 3: Update Security Groups
Route table entries allow routing, but security groups still control which ports are accessible:
# Allow RDS in VPC B to accept connections from app servers in VPC A
resource "aws_security_group_rule" "allow_vpc_a_to_rds" {
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # VPC A CIDR
security_group_id = aws_security_group.rds.id
}
Step 4: Non-Transitive Routing (Key Limitation)
VPC peering is not transitive. If A peers with B and B peers with C, A cannot reach C through B:
VPC A ←→ VPC B ←→ VPC C
A cannot reach C — VPC B does not forward A's packets to C
Solution: Peer A directly with C, or use AWS Transit Gateway (hub-and-spoke)
Step 5: CIDR Overlap Prevention
VPCs with overlapping CIDR blocks cannot peer:
VPC A: 10.0.0.0/16 ←→ VPC B: 10.0.0.0/16 ❌ Cannot peer (overlapping CIDR)
VPC A: 10.0.0.0/16 ←→ VPC B: 10.1.0.0/16 ✅ Can peer
Plan CIDR allocations before creating VPCs — changing VPC CIDRs after creation requires recreation.
Key Takeaways
- VPC peering routes traffic over the AWS private backbone — no internet gateway, NAT, or VPN required.
- Both a peering connection AND route table entries in both VPCs are required — one without the other does nothing.
- VPC peering is non-transitive: use AWS Transit Gateway for hub-and-spoke topologies connecting many VPCs.
- CIDR block overlap prevents peering — plan your IP address space before creating VPCs in production.
- Security groups remain enforced across peered VPCs — add explicit inbound rules on the destination side.
Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.