VPC Peering Deep Dive: Connect Two VPCs with Private IP Routing
When your architecture spans multiple VPCs — whether for environment isolation (dev/prod), service segmentation, or team boundaries — you need a secure, low-latency path between them that never touches the public internet. VPC Peering is AWS's native, non-transitive solution for exactly this: a private network link between two VPCs using their existing CIDR ranges.
TL;DR
| Step | Action | Where |
|---|---|---|
| 1 | Verify non-overlapping CIDRs | VPC Console / CLI |
| 2 | Create Peering Connection (Requester → Accepter) | VPC > Peering Connections |
| 3 | Accept the Peering Request | Accepter VPC account/region |
| 4 | Update Route Tables in both VPCs | VPC > Route Tables |
| 5 | Update Security Groups to allow traffic | EC2 > Security Groups |
| 6 | Enable DNS Resolution (optional but recommended) | Peering Connection settings |
Prerequisites: The Golden Rule — No CIDR Overlap
VPC Peering will fail to route correctly if the two VPCs share overlapping CIDR blocks. This is a hard constraint. Before anything else, confirm your CIDRs are distinct.
- VPC-A:
10.0.0.0/16 - VPC-B:
10.1.0.0/16
These are the values used throughout this guide. Substitute your own CIDRs accordingly.
Architecture Overview
Before touching the console or CLI, understand the data flow. A peering connection is a logical, one-to-one link. Traffic from an EC2 instance in VPC-A destined for a private IP in VPC-B must have a route table entry pointing to the peering connection as the target — and vice versa.
- EC2-A (in VPC-A, subnet
10.0.1.0/24) initiates a request to10.1.1.5. - The subnet's route table in VPC-A has an entry:
10.1.0.0/16 → pcx-xxxxxxxx. Traffic is forwarded to the peering connection. - The peering connection (
pcx-xxxxxxxx) acts as the private bridge — no IGW, no NAT, no public IPs involved. - VPC-B's route table has a matching entry:
10.0.0.0/16 → pcx-xxxxxxxx, delivering the packet to EC2-B. - EC2-B's Security Group must explicitly allow inbound traffic from
10.0.0.0/16(or the specific SG of EC2-A).
Analogy: Think of two private office buildings (VPCs) on the same city block. By default, employees must exit onto the public street to visit the other building. VPC Peering is like building a private, locked corridor directly between the two lobbies — no public exposure, no detours, direct access controlled by internal security desks (Security Groups).
Step-by-Step Implementation
Step 1: Create the Peering Connection
The VPC that initiates the request is the Requester. Since both VPCs are in the same account and region, acceptance is straightforward.
🔽 AWS CLI — Create & Accept Peering Connection
# --- Variables ---
REQUESTER_VPC_ID="vpc-aaaaaaaa" # VPC-A: 10.0.0.0/16
ACCEPTER_VPC_ID="vpc-bbbbbbbb" # VPC-B: 10.1.0.0/16
REGION="us-east-1"
# --- Step 1: Create the peering connection request ---
PEERING_ID=$(aws ec2 create-vpc-peering-connection \
--vpc-id $REQUESTER_VPC_ID \
--peer-vpc-id $ACCEPTER_VPC_ID \
--region $REGION \
--query 'VpcPeeringConnection.VpcPeeringConnectionId' \
--output text)
echo "Peering Connection ID: $PEERING_ID"
# --- Step 2: Accept the peering connection ---
# (Same account/region — can be done immediately)
aws ec2 accept-vpc-peering-connection \
--vpc-peering-connection-id $PEERING_ID \
--region $REGION
# --- Step 3: Tag it for clarity ---
aws ec2 create-tags \
--resources $PEERING_ID \
--tags Key=Name,Value="vpc-a-to-vpc-b-peering" \
--region $REGION
echo "Peering connection $PEERING_ID is now ACTIVE."
Step 2: Update Route Tables in Both VPCs
This is the most critical step. A peering connection without route table entries is inert — traffic has no path to follow. You must update route tables in both VPCs.
- VPC-A's route table gets a new entry: destination
10.1.0.0/16, targetpcx-xxxxxxxx. - VPC-B's route table gets a new entry: destination
10.0.0.0/16, targetpcx-xxxxxxxx. - Both entries are required. Missing either one results in one-way or no connectivity.
🔽 AWS CLI — Update Route Tables in Both VPCs
# --- Variables (continue from above) ---
RTB_VPC_A="rtb-11111111" # Route table associated with VPC-A subnets
RTB_VPC_B="rtb-22222222" # Route table associated with VPC-B subnets
# --- Add route in VPC-A: send 10.1.0.0/16 traffic through the peering connection ---
aws ec2 create-route \
--route-table-id $RTB_VPC_A \
--destination-cidr-block "10.1.0.0/16" \
--vpc-peering-connection-id $PEERING_ID \
--region $REGION
echo "Route added to VPC-A route table."
# --- Add route in VPC-B: send 10.0.0.0/16 traffic through the peering connection ---
aws ec2 create-route \
--route-table-id $RTB_VPC_B \
--destination-cidr-block "10.0.0.0/16" \
--vpc-peering-connection-id $PEERING_ID \
--region $REGION
echo "Route added to VPC-B route table."
Step 3: Update Security Groups
Route tables control where traffic goes. Security Groups control whether it's allowed. An instance in VPC-B must explicitly permit inbound traffic from VPC-A's CIDR (or a specific Security Group ID).
# Allow inbound traffic from VPC-A (10.0.0.0/16) on port 443 to an instance in VPC-B
SG_VPC_B="sg-bbbbbbbb" # Security group attached to EC2-B
aws ec2 authorize-security-group-ingress \
--group-id $SG_VPC_B \
--protocol tcp \
--port 443 \
--cidr "10.0.0.0/16" \
--region $REGION
echo "Security group updated for VPC-B instances."
Least Privilege Tip: Instead of allowing the entire VPC-A CIDR, reference the specific Security Group ID of the source instances in VPC-A. This is more precise and reduces blast radius.
# More precise: allow traffic from a specific SG in VPC-A
SG_VPC_A="sg-aaaaaaaa"
aws ec2 authorize-security-group-ingress \
--group-id $SG_VPC_B \
--protocol tcp \
--port 443 \
--source-group $SG_VPC_A \
--region $REGION
Step 4: Enable DNS Resolution (Recommended)
By default, DNS hostnames from one VPC are not resolvable in the peer VPC. Enable this so instances can resolve each other's private DNS names across the peering link.
# Enable DNS resolution from VPC-A to VPC-B
aws ec2 modify-vpc-peering-connection-options \
--vpc-peering-connection-id $PEERING_ID \
--requester-peering-connection-options AllowDnsResolutionFromRemoteVpc=true \
--region $REGION
# Enable DNS resolution from VPC-B to VPC-A
aws ec2 modify-vpc-peering-connection-options \
--vpc-peering-connection-id $PEERING_ID \
--accepter-peering-connection-options AllowDnsResolutionFromRemoteVpc=true \
--region $REGION
Verification
After all steps are complete, SSH into an EC2 instance in VPC-A and test connectivity to a private IP in VPC-B.
# From EC2-A (10.0.1.10), ping EC2-B's private IP
ping 10.1.1.5
# Or test a specific port with nc (netcat)
nc -zv 10.1.1.5 443
If the ping fails, work through this checklist:
- ✅ Peering connection status is Active (not Pending-Acceptance)
- ✅ Route table entries exist in both VPCs pointing to the peering connection ID
- ✅ The correct route table is associated with the relevant subnets
- ✅ Security Groups on the target instance allow inbound from the source CIDR/SG
- ✅ Network ACLs (NACLs) on both subnets are not blocking the traffic (NACLs are stateless)
Key Limitations to Know
| Limitation | Detail |
|---|---|
| Non-transitive | If VPC-A peers with VPC-B and VPC-B peers with VPC-C, VPC-A cannot reach VPC-C through VPC-B. Each pair needs its own peering connection. |
| No overlapping CIDRs | Hard requirement. Plan your IP address space before deploying VPCs. |
| No edge-to-edge routing | You cannot route traffic from a peered VPC through an IGW, VPN, or Direct Connect gateway attached to the other VPC. |
| Scalability | At scale (many VPCs), peering becomes a mesh problem. Consider AWS Transit Gateway for hub-and-spoke topologies. |
IAM Permissions Required
The IAM principal executing these commands needs the following minimum permissions:
{
"Effect": "Allow",
"Action": [
"ec2:CreateVpcPeeringConnection",
"ec2:AcceptVpcPeeringConnection",
"ec2:CreateRoute",
"ec2:ModifyVpcPeeringConnectionOptions",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CreateTags",
"ec2:DescribeVpcPeeringConnections",
"ec2:DescribeRouteTables"
],
"Resource": "*"
}
Glossary
| Term | Definition |
|---|---|
| VPC Peering Connection | A networking connection between two VPCs enabling private IP routing. Identified by a pcx- prefix. |
| Route Table | A set of rules (routes) that determine where network traffic from a subnet is directed. |
| Non-transitive Peering | Traffic cannot flow through an intermediate VPC to reach a third VPC — each pair must have a direct peering connection. |
| CIDR Block | Classless Inter-Domain Routing notation defining the IP address range of a VPC (e.g., 10.0.0.0/16). |
| Security Group | A stateful virtual firewall controlling inbound and outbound traffic at the instance level. |
Next Steps
VPC Peering is the right tool for simple, point-to-point VPC connectivity. If your architecture grows to 5+ VPCs, evaluate AWS Transit Gateway for centralized, scalable routing. For the official VPC Peering documentation, refer to the AWS VPC Peering Guide.
Comments
Post a Comment