AWS VPC Peering Step by Step - Connect Two VPCs (Same or Different Account and Region), Accept the Request, Add Routes, Security Groups, DNS Resolution, Test with EC2, and the Limits (AWS Part-12)
In Part-5 we built one VPC. Real accounts have several - a dev VPC and a prod VPC, a VPC per team, a shared-services VPC with the monitoring and the directory - and sooner or later an instance in one needs to talk to a database in another privately, without going out to the internet and back in. The simplest way to do that is VPC peering.
This is Part-12. We peer two VPCs, do the two things everybody forgets (routes on both sides, security groups on both sides), test with EC2, and then look at cross-account and cross-region peering, DNS, the limits that make peering stop scaling, and the point where Transit Gateway, Part-13 takes over. I also have the Google Cloud version of this - GCP VPC peering - if you work on both clouds; the concepts map almost one to one.
Table of Content
- What VPC peering is and how it works
- Prerequisites - two VPCs with non-overlapping CIDRs
- Step 1 - Create the peering connection (requester)
- Step 2 - Accept the peering connection (accepter)
- Step 3 - Add routes in both VPCs
- Step 4 - Open the security groups
- Step 5 - Enable DNS resolution over the peering
- Step 6 - Test it with EC2
- Cross-account and cross-region peering
- The rules - not transitive, no edge-to-edge routing, quotas
- What it costs
- The AWS CLI equivalents
- Common VPC peering errors and how to fix them
- Peering vs Transit Gateway - when to switch
- Conclusion
1. What VPC peering is and how it works
A VPC peering connection is a networking connection between two VPCs that lets them route traffic to each other using private IPv4 or IPv6 addresses, as if they were in the same network. From the how it works page -
- The owner of the requester VPC sends a request to the owner of the accepter VPC.
- The accepter accepts it - the connection becomes
Active. - Each side adds a route in its route tables pointing the other VPC's CIDR at the peering connection.
- Security groups are updated so that traffic from the peer is allowed.
- Optionally, DNS resolution is enabled so that public hostnames resolve to private IPs across the peering.
The traffic never leaves the AWS network, there is no gateway, VPN device or physical hardware in the path - the docs note no single point of failure for communication or a bandwidth bottleneck - and between regions it is encrypted.
2. Prerequisites - two VPCs with non-overlapping CIDRs
The one hard requirement - the two VPCs must not have overlapping CIDR blocks. 10.0.0.0/16 and 10.0.0.0/16 cannot be peered, ever; 10.0.0.0/16 and 10.0.128.0/17 cannot either. If both VPCs have multiple CIDRs, none may overlap. This is why planning CIDRs in Part-5 mattered - and why every account that used the default VPC's 172.31.0.0/16 for real workloads eventually hits this wall.
For this tutorial -
| VPC | CIDR | Role |
|---|---|---|
jhooq-vpc (VPC A) | 10.0.0.0/16 | requester - the VPC from Part-5, with an EC2 instance in jhooq-private-1a |
jhooq-data-vpc (VPC B) | 10.1.0.0/16 | accepter - create it with VPC and more, 2 AZs, 0 public subnets, 2 private subnets, no NAT, and launch a t3.micro in a private subnet |
Both in eu-central-1, same account, to start. (Cross-account and cross-region in section 9.)
3. Step 1 - Create the peering connection (requester)
VPC → Peering connections → Create peering connection (docs) -
- Name -
jhooq-vpc-to-data-vpc. - VPC ID (Requester) -
jhooq-vpc. - Select another VPC to peer with → Account - My account (or Another account and the 12-digit account ID).
- Region - This Region (or Another Region and pick it).
- VPC ID (Accepter) -
jhooq-data-vpc. Within the same account and region the dropdown lists your VPCs; for another account you type the VPC ID. - Create peering connection.
The status is Pending acceptance. The requester can still delete the request at this point; if nobody acts, it expires after 7 days.
4. Step 2 - Accept the peering connection (accepter)
Same account - select the connection → Actions → Accept request → Accept request. Another account - the accepter's admin opens Peering connections in their console (in the accepter's region), finds the pending request with your account ID, and accepts it. Status goes Provisioning → Active.
The console then shows a helpful banner - Modify my route tables now - which takes you to the next step. The connection ID (pcx-0123456789abcdef0) is what the routes will point to.
5. Step 3 - Add routes in both VPCs
An Active peering connection carries no traffic until both route tables know about it - the step most people do on one side only.
In VPC A (jhooq-vpc) - Route tables → jhooq-private-rt → Routes → Edit routes → Add route - Destination 10.1.0.0/16, Target → Peering Connection → pcx-0123.... Save. Repeat for jhooq-public-rt if public-subnet instances also need to reach VPC B.
110.0.0.0/16 local
20.0.0.0/0 nat-...
310.1.0.0/16 pcx-0123456789abcdef0
In VPC B (jhooq-data-vpc) - its private route table(s): Destination 10.0.0.0/16, Target pcx-0123....
110.1.0.0/16 local
210.0.0.0/16 pcx-0123456789abcdef0
You can route a subset instead of the whole CIDR - 10.0.11.0/24 only - to let just one subnet of A reach B. Routing uses the most specific match, so a /24 route beats the /16; the routing configurations page has the patterns. For cross-region peering, add the routes in each region's console.
6. Step 4 - Open the security groups
Routes get the packet to the subnet; the security group on the destination instance decides whether it gets in. On the database instance in VPC B, add an inbound rule -
- Type PostgreSQL (5432) or SSH (22) for the test, Source
10.0.0.0/16- or narrower,10.0.11.0/24. - Same region only - you can instead reference the peer VPC's security group ID as the source (
sg-0abc... /111111111111in another account). The security groups page confirms this works within one region; across regions you must use CIDRs.
For ping tests also allow All ICMP - IPv4 from the peer CIDR. The security group on the source instance allows all outbound by default, so nothing to do there. Also check network ACLs if you customised them - the default allows everything.
7. Step 5 - Enable DNS resolution over the peering
By default, if an instance in VPC A resolves the public DNS name of an instance in VPC B (ec2-3-70-1-2.eu-central-1.compute.amazonaws.com), it gets the public IP - and the traffic would try to go via the internet, not the peering. Select the connection → Actions → Edit DNS settings → tick Requester DNS resolution and Accepter DNS resolution → Save. Now those hostnames resolve to private IPs on both sides. Both VPCs need DNS hostnames and DNS resolution enabled (Part-5, Step 1). Each VPC owner enables their own side; in cross-account peering that is two people.
8. Step 6 - Test it with EC2
From the app instance in VPC A (reach it via the bastion or Session Manager as in Part-5) -
1# the database instance's private IP in VPC B
2ping -c 3 10.1.11.25
3# PING 10.1.11.25 ... 64 bytes from 10.1.11.25: icmp_seq=1 ttl=64 time=0.6 ms
4
5# port level
6nc -zv 10.1.11.25 5432
7# Connection to 10.1.11.25 5432 port [tcp/postgresql] succeeded!
8
9# traceroute shows one hop - there is no gateway in between
10traceroute 10.1.11.25
11
12# DNS resolution over peering - the public hostname returns the private IP
13dig +short ec2-3-70-1-2.eu-central-1.compute.amazonaws.com
14# 10.1.11.25
If ping times out, go straight to section 13 - it is always a route missing on one side or a security group.
9. Cross-account and cross-region peering
Cross-account - identical steps; in Step 1 choose Another account and enter the account ID (and VPC ID). The accepter sees the request in their console. Both sides add routes and security group rules in their own account. This is how a vendor gives you private access to their service VPC, and how the member accounts of an organization share a central VPC - though for many accounts, Transit Gateway or AWS RAM-shared subnets scale better.
Cross-region (inter-region) - choose Another Region in Step 1. Traffic is encrypted on the AWS backbone, the MTU is 8,500 bytes instead of 9,001, you cannot reference peer security groups (CIDRs only), DNS resolution must be enabled explicitly even for RFC 1918 ranges, and you pay the inter-region data transfer rate. Useful for disaster recovery replication and for reaching a region-specific service privately.
10. The rules - not transitive, no edge-to-edge routing, quotas
Straight from the limitations -
- Not transitive. If A is peered with B and A with C, B cannot reach C through A. Each pair that needs to talk needs its own connection. Ten VPCs that all need to reach each other = 45 peering connections and 90 route table entries; that is the scaling wall.
- No edge-to-edge routing. B cannot use A's Internet Gateway, NAT Gateway, VPN, Direct Connect or S3 gateway endpoint. Peering connects VPC-to-VPC, nothing beyond.
- One connection per VPC pair, no overlapping CIDRs (any of them), no querying the peer's Amazon DNS server directly.
- Quotas - 50 active peering connections per VPC by default, adjustable up to 125; 25 outstanding requests per VPC (quotas).
- Lifecycle -
initiating-request → pending-acceptance → provisioning → active, orfailed,rejected,expired(7 days),deleted; failed/deleted connections stay visible for a while. - Shared VPCs - only the VPC owner can create or accept peering, not participants.
11. What it costs
A peering connection itself is free - no hourly charge. You pay only for data transfer across it: traffic between the same AZ is free, traffic between AZs in the same region is charged at the inter-AZ rate ($0.01 per GB in each direction in most regions), and inter-region traffic at the inter-region data transfer rate (from $0.02 per GB). Compare that with Transit Gateway's per-attachment-hour plus per-GB processing charge - for two or three VPCs, peering is by far the cheapest option.
12. The AWS CLI equivalents
1# 1. request (same account & region; add --peer-owner-id / --peer-region for cross-account / cross-region)
2PCX=$(aws ec2 create-vpc-peering-connection --vpc-id vpc-0aaa --peer-vpc-id vpc-0bbb \
3 --tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=jhooq-vpc-to-data-vpc}]' \
4 --query VpcPeeringConnection.VpcPeeringConnectionId --output text)
5
6# 2. accept (run as the accepter, in the accepter's region)
7aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id "$PCX"
8
9# 3. routes on both sides
10aws ec2 create-route --route-table-id rtb-privateA --destination-cidr-block 10.1.0.0/16 --vpc-peering-connection-id "$PCX"
11aws ec2 create-route --route-table-id rtb-privateB --destination-cidr-block 10.0.0.0/16 --vpc-peering-connection-id "$PCX"
12
13# 4. security group on the database instance
14aws ec2 authorize-security-group-ingress --group-id sg-0db --protocol tcp --port 5432 --cidr 10.0.0.0/16
15
16# 5. DNS resolution both ways
17aws ec2 modify-vpc-peering-connection-options --vpc-peering-connection-id "$PCX" \
18 --requester-peering-connection-options AllowDnsResolutionFromRemoteVpc=true \
19 --accepter-peering-connection-options AllowDnsResolutionFromRemoteVpc=true
20
21aws ec2 describe-vpc-peering-connections --vpc-peering-connection-ids "$PCX" --query 'VpcPeeringConnections[0].Status'
In Terraform: aws_vpc_peering_connection (and aws_vpc_peering_connection_accepter for cross-account, with a second provider alias as in Terraform and AWS multi-account setup), aws_route on both sides, aws_vpc_peering_connection_options for DNS.
13. Common VPC peering errors and how to fix them
1. The CIDR block ... overlaps with the CIDR block ... of the peer VPC / VpcPeeringConnectionAlreadyExists on create - Overlapping ranges, or a connection between these two VPCs already exists (maybe pending). Re-IP one VPC (new VPC with a different CIDR - CIDRs cannot be changed) or use a private NAT Gateway / Transit Gateway with NAT to bridge overlapping networks.
2. ping / nc times out although the connection is Active - 95%: a route is missing on one side, or the route is in the wrong route table (the subnet of the instance uses the main table, you edited the custom one). Check Subnet associations on both tables. Then the destination security group and NACL.
3. The connection stays Pending acceptance - Nobody accepted it; in cross-account peering the accepter must do it in their account and region. After 7 days it expires - create it again.
4. Failed state - Usually overlapping CIDRs discovered at provisioning or the peer VPC was deleted. The failed entry disappears after ~2 hours.
5. Instances resolve the peer's hostname to a public IP and traffic goes nowhere - DNS resolution over the peering is off (section 7), or DNS hostnames are disabled on one VPC.
6. You cannot reference a security group in another region / the SG dropdown does not show the peer's groups - Inter-region peering - use CIDRs. Same region but another account: type the ID as sg-0abc.../<account-id>.
7. VPC B cannot reach the internet / S3 through VPC A's NAT or endpoint - Edge-to-edge routing is not supported. Give B its own NAT Gateway / gateway endpoint, or centralise egress with a Transit Gateway.
8. Only some subnets can reach the peer - Each route table needs the route. Private and public subnets have different tables; add it to every table whose subnets need the peer.
9. Return traffic drops (one direction works) - An asymmetric route - the peer routes back via a more specific route to another target, or a NACL blocks the ephemeral return ports (NACLs are stateless).
10. VpcPeeringConnectionLimitExceeded - 50 active per VPC. Request an increase (max 125) or move to Transit Gateway.
14. Peering vs Transit Gateway - when to switch
| VPC peering | Transit Gateway | |
|---|---|---|
| Topology | point-to-point, one connection per pair | hub-and-spoke, one attachment per VPC |
| Transitive | no | yes - and you control it with route tables |
| On-premises (VPN / Direct Connect) sharing | no (no edge-to-edge) | yes - attach once, every VPC can use it |
| Centralised egress / inspection | no | yes |
| Bandwidth | no limit on the connection | up to 100 Gbps per VPC attachment per AZ |
| Cost | free, data transfer only | per attachment-hour + per GB processed |
| Setup for 3 VPCs | 3 connections, 6 routes | 3 attachments, 3 routes + TGW route table |
| Setup for 10 VPCs | 45 connections, 90 routes | 10 attachments |
Rule of thumb from the field - two or three VPCs that need to talk: peer them. More than a handful, a shared on-premises connection, or any need for centralised firewalls or egress: Transit Gateway, which is exactly Part-13.
15. Conclusion
To summarise Part-12 -
- VPC peering privately connects two VPCs with non-overlapping CIDRs, in the same or another account or region, over the AWS backbone with no gateway in the path.
- The flow is request → accept → routes on both sides → security groups → DNS resolution - and the two things people forget are the route in the second VPC and the security group on the destination.
- It is not transitive and has no edge-to-edge routing - no sharing of the peer's Internet Gateway, NAT, VPN, Direct Connect or endpoints.
- The connection is free; you pay inter-AZ or inter-region data transfer only.
- For more than a few VPCs or a shared on-premises link, move to Transit Gateway.
The official references are the VPC Peering Guide, how VPC peering works, routing configurations and peering quotas. Next: AWS Transit Gateway, Part-13 - the hub that removes the 45-connection mesh.
AWS step by step series -
- Part-1 : AWS IAM user - create a user, group, policy, access keys and MFA
- Part-2 : AWS Organizations - multi-account setup, OUs and SCPs
- Part-3 : AWS assume IAM role - trust policy, switch role in console and CLI
- Part-4 : How to launch an EC2 instance - key pair, security group, SSH
- Part-5 : AWS VPC - public and private subnets, Internet Gateway, NAT Gateway, route tables
- Part-8 : EC2 launch template - versions, default version, source template, SSM parameter AMI
- Part-10 : EC2 Auto Scaling - launch template, Auto Scaling group, target tracking, ALB
- Part-11 : AWS WAF - web ACL, managed rules, rate limiting, geo blocking
- Part-12 : AWS VPC Peering - connect two VPCs, routes, security groups, DNS
- Part-13 : AWS Transit Gateway - hub-and-spoke for many VPCs and on-premises
- Part-14 : AWS NAT Gateway deep dive - public vs private, limits, cost, troubleshooting
- Part-15 : Amazon Route 53 - hosted zones, records, alias, routing policies, health checks
- Part-16 : AWS security groups - inbound and outbound rules, stateful, referencing, quotas
- Part-16 : AWS Certificate Manager - free TLS certificates for ALB, CloudFront and API Gateway
- Part-17 : AWS Lambda - function URLs, environment variables and layers
- Part-18 : Network Load Balancer - setup, and ALB vs NLB
- Part-19 : VPC endpoints - gateway and interface endpoints (PrivateLink) instead of NAT
- Part-20 : AWS PrivateLink - publish your own service with an endpoint service and NLB
- Part-20 : Amazon EBS volumes - types, attach, mount, resize, snapshots, encryption
- Part-21 : VPC Flow Logs - CloudWatch Logs, S3, record format, Logs Insights, Athena
- Part-21 : EC2 Spot Instances - pricing, interruptions, mixed instances groups
- Part-24 : AWS Control Tower - landing zone, controls, Account Factory, Identity Center
Networking fundamentals -
- What is a VPC and a subnet? AWS networking in five minutes
- What is CIDR? Calculate IP ranges for VPCs and subnets
- What is NAT? Static NAT, dynamic NAT and PAT explained
More AWS guides -
- What is AWS CloudFormation? Templates, stacks, change sets, drift, StackSets
- Learn AWS S3 - the complete course
- AWS API Gateway - REST API with Lambda, authorizers, Terraform
- AWS Advanced Networking Specialty (ANS-C01) - course companion
- AWS ECS and Fargate - how to deploy a Docker container
- AWS S3 - how to host a static website
- Terraform create EC2 instance on AWS
- Terraform AWS IAM - users, roles and policies
- Terraform and AWS multi-account setup
- Terraform - setting up an ALB and SSL
Posts in this series
- Amazon EBS Volumes Step by Step - Volume Types Compared (gp3, gp2, io2 Block Express, st1, sc1), Create, Attach, Format and Mount a Volume, Resize Without Downtime, Snapshots, Encryption, Multi-Attach, Pricing and Troubleshooting (AWS Part-20)
- Amazon Route 53 Step by Step - Hosted Zones, Record Types, Alias Records, Point a Domain at an ALB, Routing Policies (Weighted, Latency, Failover, Geolocation), Health Checks, Private Zones and Pricing (AWS Part-15)
- AWS Advanced Networking - Free 8-Hour Full Course Companion (VPC, NAT Gateway, Bastion, ALB, NLB, WAF, VPC Peering, Transit Gateway, VPC Endpoints and PrivateLink, Route 53, ACM) with Timestamps and the ANS-C01 Exam Facts
- AWS Assume IAM Role Step by Step - Trust Policy vs Permissions Policy, Switch Role in the Console, aws sts assume-role, CLI Profiles, Cross-Account Access, MFA and External ID (AWS Part-3)
- AWS Certificate Manager (ACM) Step by Step - Request a Free TLS Certificate, DNS Validation with Route 53, Attach It to an ALB HTTPS Listener, Redirect HTTP to HTTPS, CloudFront and API Gateway, Auto-Renewal, Exportable Certificates and ACME (AWS Part-16)
- AWS Control Tower Step by Step - Set Up a Landing Zone, Security OU with Log Archive and Audit Accounts, Controls (Guardrails), Region Deny, IAM Identity Center, Account Factory and Enrolling Existing Accounts (AWS Part-24)
- AWS EC2 Auto Scaling Step by Step - Launch Template, Auto Scaling Group Across Two AZs, Target Tracking Policy, Application Load Balancer, Health Checks and Instance Refresh (AWS Part-10)
- AWS EC2 Launch Template Step by Step - Create a Template, Versions and the Default Version, Source Template, Create From a Running Instance, Systems Manager Parameter Instead of an AMI ID, Launch Templates vs Launch Configurations, IAM Guardrails, CLI and Terraform (AWS Part-8 and Part-17)
- AWS EC2 Spot Instances Step by Step - How Spot Pricing Works, Launch a Spot Instance, Interruptions and the Two-Minute Notice, Rebalance Recommendations, Stop vs Hibernate vs Terminate, Spot in Auto Scaling Mixed Instances Groups, Billing Rules, Best Practices, CLI and Terraform (AWS Part-21)
- AWS IAM User Step by Step - Create a User, User Group, Attach Policies, Access Keys, MFA and Sign-in URL (AWS Part-1)
- AWS Lambda Step by Step - Create a Function, Function URL (HTTPS Endpoint Without API Gateway), Environment Variables, Lambda Layers for Python Dependencies, Versions and Aliases, Limits, Pricing and Errors (AWS Part-17)
- AWS NAT Gateway Deep Dive - How It Works, Public vs Private NAT Gateway, Setup Step by Step, Limits (55,000 Connections, 100 Gbps), CloudWatch Metrics, Cost Optimisation, NAT Instance Comparison and Troubleshooting (AWS Part-14)
- AWS Network Load Balancer Step by Step - Create an NLB with Static IPs, Target Groups, TCP and TLS Listeners, Security Groups, Client IP Preservation, Cross-Zone Load Balancing, and ALB vs NLB Explained (AWS Part-18)
- AWS Organizations Step by Step - Multi-Account Setup, Organizational Units, Service Control Policies (SCPs), Consolidated Billing and Identity Center (AWS Part-2)
- AWS PrivateLink Step by Step - Publish Your Own Service with a VPC Endpoint Service and Network Load Balancer, Allow Consumers, Accept Connections, Private DNS Name, Cross-Account and Cross-Region, Pricing and Troubleshooting (AWS Part-20)
- AWS Security Groups Step by Step - Inbound and Outbound Rules, Stateful Behaviour, Referencing Security Groups, the Three-Tier ALB-Web-DB Pattern, Quotas, Security Group vs Network ACL, CLI and Terraform (AWS Part-16)
- AWS Transit Gateway Step by Step - Connect Many VPCs and On-Premises Through One Hub, VPC Attachments, Transit Gateway Route Tables, Associations and Propagations, Isolation, Peering, Pricing (AWS Part-13)
- AWS VPC Endpoints Step by Step - Gateway Endpoints for S3 and DynamoDB, Interface Endpoints (PrivateLink) for SSM, ECR and Other Services, Private DNS, Endpoint Policies, Security Groups, Cost vs NAT Gateway, and Troubleshooting (AWS Part-19)
- AWS VPC Flow Logs Step by Step - Enable Flow Logs for a VPC, Subnet or Network Interface, Publish to CloudWatch Logs or S3, Read a Flow Log Record Field by Field, Custom Formats, Query with Logs Insights and Athena, Find Rejected Traffic, Pricing and Limitations (AWS Part-21)
- AWS VPC Peering Step by Step - Connect Two VPCs (Same or Different Account and Region), Accept the Request, Add Routes, Security Groups, DNS Resolution, Test with EC2, and the Limits (AWS Part-12)
- AWS VPC Step by Step - Create a VPC with Public and Private Subnets, Internet Gateway, NAT Gateway and Route Tables (and Test It with EC2) (AWS Part-5)
- AWS WAF Step by Step - Create a Web ACL, Attach It to an ALB or API Gateway, AWS Managed Rules, Rate-Based Rules, Geo Blocking, IP Sets, Count Mode and Logging (AWS Part-11)
- How to Launch an EC2 Instance on AWS Step by Step - AMI, Instance Type, Key Pair, Security Group, Connect with SSH or EC2 Instance Connect, Stop vs Terminate (AWS Part-4)
- What is an AWS VPC and a Subnet? Virtual Private Cloud Explained in Five Minutes (Region, Availability Zones, Public vs Private Subnets, Gateways, Route Tables)
- What is AWS CloudFormation? Templates, Stacks and Change Sets Explained, Template Anatomy Section by Section, Create Your First Stack Step by Step, Update With a Change Set, Drift Detection, Nested Stacks and StackSets, Quotas, Pricing, CLI, and CloudFormation vs Terraform
- What is CIDR (Classless Inter-Domain Routing)? How to Calculate IP Ranges for VPCs and Subnets, with Examples (/8, /16, /24, /28, /32)
- What is NAT (Network Address Translation)? How It Works, Static NAT vs Dynamic NAT vs PAT, the Translation Table, and Where NAT Shows Up in AWS
- AWS API Gateway Tutorial - REST API with Lambda Proxy and Non-Proxy Integration, Request Validation, HTTP API vs REST API, Resource Policies, Lambda Authorizers and Terraform
- Learn AWS S3 - The Complete Course (Buckets, Objects, Storage Classes, Lifecycle, Versioning, Security Defaults, Bucket Policies, Static Hosting, CLI and Terraform)
- How to release(delete) Elastic IP from AWS?
- Fix docker login 'error saving credentials: error storing credentials - err: exit status 1' (AWS ECR on macOS, Windows, Linux and WSL)