22.6 VPC Peering: Non-Transitive Private Connectivity Between VPCs
Right, so you’ve built your VPCs, carved them into subnets, and set up your routing tables like a pro. Now you need two of these private networks to talk to each other. You might instinctively think, “I’ll just set up a VPN,” and you could, but that’s like using a sledgehammer to crack a nut when AWS has a perfectly good nutcracker sitting right there: VPC Peering.
It’s a beautifully simple concept on the surface: a direct, encrypted network connection between two VPCs that allows you to route traffic between them using private IP addresses. No gateways, no VPNs, no internet. Just clean, private connectivity. But of course, this being AWS, “simple” always has a few devilish details lurking in the fine print. Let’s get into it.
The Non-Transitive Routing Gotcha
This is the single most important thing to internalize, so I’m hitting you with it first. VPC peering is non-transitive. Say it with me: non-transitive. If VPC-A is peered with VPC-B, and VPC-B is peered with VPC-C, this does not mean VPC-A can talk to VPC-C through VPC-B. VPC-B will not, under any circumstances, act as a middleman and forward traffic from A to C.
Why on earth would they design it this way? It’s not madness; it’s a brutal, elegant form of simplicity and security. It keeps the routing tables and the trust relationships dead simple and explicit. You want A to talk to C? You peer them directly. This architecture forces you to think about your network topology and explicitly define every single connection, which prevents you from accidentally creating a sprawling, insecure mess you can’t untangle later.
The CIDR Block Conundrum
You cannot peer two VPCs whose IP address ranges (CIDR blocks) overlap. It’s an instant, hard fail. The routing tables would have an absolute meltdown trying to figure out where to send a packet destined for 10.0.1.5 if that address exists in both VPCs. It’s like trying to send a letter to “John Smith” without a house number—utterly useless. Before you even think about creating a peering connection, ensure your VPC CIDRs are unique across your entire peered landscape. This is non-negotiable.
Creating a Peering Connection: The Theory and The Practice
The process is straightforward. You create a peering connection, the owner of the accepter VPC (the one you’re requesting to connect to) must accept it, and then you both must add routes to your respective route tables to send traffic destined for the other VPC’s CIDR block through the peering connection.
Let’s make it concrete. Imagine you have a Dev-VPC (10.0.0.0/16) and a Prod-VPC (10.1.0.0/16). You, as the owner of Dev-VPC, want to peer to Prod-VPC.
First, you create the peering request. Here’s how you’d do it with the AWS CLI, because the console is just a GUI wrapper around these API calls anyway.
# You run this from your account, specifying the accepter's VPC and account ID
aws ec2 create-vpc-peering-connection \
--vpc-id vpc-0a1b2c3d4e5f67890 \ # Your Dev-VPC ID
--peer-vpc-id vpc-1b2c3d4e5f67890a \ # The Prod-VPC ID
--peer-owner-account-id 123456789012 # The Prod account ID
This command will output a VpcPeeringConnectionId (something like pcx-0a1b2c3d4e5f67890). Now, the owner of the Prod account must accept it.
# The Prod account owner runs this
aws ec2 accept-vpc-peering-connection \
--vpc-peering-connection-id pcx-0a1b2c3d4e5f67890
The peering connection is now active. But nothing will work yet. Why? Because the routers are still clueless. You both need to tell your respective route tables what to do.
Updating the Route Tables: Making the Introduction
In your Dev-VPC, you need to edit the route table associated with your private subnets. You’ll add a route that says “Any traffic destined for 10.1.0.0/16 (the Prod-VPC), send it to the peering connection pcx-0a1b2c3d4e5f67890.”
Conversely, the Prod admin needs to edit their route tables, adding a route that says “Traffic for 10.0.0.0/16 (the Dev-VPC), send it to that same peering connection.”
Here’s the route table update for the Dev side:
aws ec2 create-route \
--route-table-id rtb-0a1b2c3d4e5f67891 \ # Your Dev private RT ID
--destination-cidr-block 10.1.0.0/16 \
--vpc-peering-connection-id pcx-0a1b2c3d4e5f67890
And that’s it. If your Security Groups and Network ACLs are configured to allow the traffic (a big ‘if’—always check them first when troubleshooting), an EC2 instance in Dev-VPC at 10.0.1.100 should now be able to ping an instance in Prod-VPC at 10.1.1.100.
The Security Group Head-Scratcher
Ah, Security Groups. You’d think that since the traffic is private and over a peering connection, you could just reference the peer VPC’s CIDR block in your SG rules. And you can. But the far more secure and elegant way is to reference the security group ID of the instances in the peer VPC.
Yes, you can reference a security group from another VPC in the same region, even if it’s in a different account. This is brilliant because it’s identity-based, not just IP-based. You’re saying “allow traffic from any instance that has this specific security group attached,” which is much more maintainable than hardcoding IP ranges that might change.
Your inbound rule on the Prod instance’s SG would have a source like sg-0b1c2d3e4f567890a (the Dev instance’s SG ID). You must specify the owner’s account ID for cross-account peering. This is a best practice that too many people overlook.
The Cross-Account Consideration
VPC peering works seamlessly across AWS accounts. The process is identical; you just need the accepter VPC’s account ID when creating the request. The accepter account must, well, accept it. This is incredibly powerful for creating shared services architectures or for contractors to access a client’s environment without a janky VPN setup.
The final, critical piece of advice: never, ever forget the route table updates. I’ve lost count of the hours I’ve spent troubleshooting only to find a perfectly active peering connection and utterly vacant route tables. The connection is the introduction; the route tables are the instructions on what to do with it. You need both. Now go forth and peer responsibly.