AWS VPC Peering: Connect Two VPCs with a Private Link!
As AWS environments grow, a single VPC is rarely enough. Applications may run in separate VPCs for security, project isolation, or organizational reasons. At this point, VPC Peering allows us to connect two VPCs over a secure, private link.
Why VPC Peering?
- Low latency: Traffic flows through the AWS backbone, not over the internet.
- Security: Communication happens entirely over private IPs.
- Simplicity: You don’t need Transit Gateway or VPN — two VPCs can talk directly.
⚠️ Keep in mind:
- No transit routing: If A→B and B→C exist, A cannot directly reach C.
- CIDR blocks must not overlap.
- Route tables must be updated on both sides.
What You Will Learn
In this lab:
- Create two VPCs in the same region (us-east-1 / N. Virginia):
- VPC-A: Public subnet with a bastion EC2.
- VPC-B: Private subnet with no public access.
- Build a VPC Peering Connection between them.
- Connect from the bastion to the private EC2 using private IP and SSH.
- Clean up resources to avoid unnecessary costs.
Scenario and Architecture
VPC-A (Public)
- CIDR:
10.0.0.0/16 - Public Subnet:
10.0.1.0/24 - EC2:
VPC-A_EC2 - Has Public IP
- Works as Bastion
- SSH + HTTP open
VPC-B (Private)
- CIDR:
20.0.0.0/16 - Private Subnet:
20.0.0.0/24 - EC2:
VPC-B_EC2 - No Public IP
- Only internal SSH access
👉 Goal: SSH into the private EC2 in VPC-B through the bastion EC2 in VPC-A, using its private IP.
Step-by-step hands-on lab to connect a public bastion to a private EC2 with VPC Peering
Section 1 — VPC-A (Public) Setup
First, we will create VPC-A, which has internet access. In this VPC, we will set up a public subnet, attach an Internet Gateway for internet access, and finally launch a bastion EC2 instance. Through this bastion host, we will connect to the other VPC.
1. Create VPC
VPC →Your VPC → CreateVPC
- Log in to the AWS Console.
- Select the region N. Virginia (us-east-1).
- From the menu, go to VPC → Your VPCs → Create VPC.
- Option: VPC only
- Name: VPC-A
- IPv4 CIDR: 10.0.0.0/16
- Leave the other settings as default and click Create VPC.
From the list, select VPC-A → Actions → Edit VPC settings.
Check the boxes for Enable DNS resolution and Enable DNS hostnames, then click Save.
✅ VPC-A is now ready. DNS resolution and hostnames settings are enabled.
2. Create Public Subnet
Go to Subnets → Create subnet.
- VPC ID: VPC-A
- Subnet name: Public-Subnet-A
- Availability Zone: No Preference
IPv4 subnet CIDR block : 10.0.1.0/24
Leave the Availability Zone as default and click the Create subnet button.
3. Attach Internet Gateway
For the EC2 instance in the public subnet to access the internet, an Internet Gateway (IGW) is required.
Go to Internet Gateways → Create internet gateway
- Name: IGW-A
After creating it, select Attach to VPC → VPC-A.
4. Create Public Route Table
We will create a route table to direct the traffic of the public subnet to the IGW.
Go to Route tables → Create route table
- Name: PublicRT-A
- VPC: VPC-A
After creating the route table, go to Subnet associations → select Public-Subnet-A and save.
Edit routes → Add route
Destination: 0.0.0.0/0 Target: IGW-A
5. Launch Bastion EC2
Now let’s create a test server inside the public subnet.
- Region: us-east-1 (N. Virginia)
- Go to Services → EC2 → Instances → Launch instances
- Name and tags:
- Name: VPC-A_EC2
- Application and OS Images (AMI):
- Quick Start → Amazon Linux 2023 (kernel-6.1)
Key Pair (login):
- Select Create new key pair → Key pair name:
ec2_ssh_key - Type: RSA, File format: .pem → Click Create and select it.
Save the key file to your computer.
Network settings → Edit:
- VPC: VPC-A
- Subnet: default (Public-Subnet-A should be pre-selected)
- Auto-assign public IP: Enable
Firewall (SG): Create a new security group
- Security group name: Public_EC2_SG
- Description: Security group for public EC2
Rules:
- SSH → Source: Anywhere (0.0.0.0/0)
- HTTP → Source: Anywhere
Advanced details:
User Data Script (optional, to display a simple web page):
#!/bin/bash
sudo dnf update -y
sudo dnf install httpd -y
systemctl start httpd
systemctl enable httpd
echo "<html><h1> Welcome to Tech Istanbul: VPC Peering Lab</h1></html>" > /var/www/html/index.htmlWhen you click the Launch instances button, your bastion server will be ready within a few minutes. Paste the Public IP into your browser to see the test page.
Note down the IPv4 Public IP address of the EC2 instance.
When you paste this IP into your browser, you should see the welcome page.
Section 2: VPC-B (Private) Setup
Now we will create our second VPC, VPC-B. This VPC will contain only a private subnet, and the EC2 instance will not have a public IP. It will be accessible only from inside, through the bastion.
1. Create VPC
Go to VPC → Your VPCs → Create VPC.
- Option: VPC only
- Name: VPC-B
- IPv4 CIDR: 20.0.0.0/16
- Click Create VPC.
✅ The second VPC is now ready.
From the list, select VPC-B → Actions → Edit VPC settings →
Check Enable DNS resolution and Enable DNS hostnames, then click Save.
2. Create Private Subnet
This subnet will not have internet access; it will be used only for internal communication.
Go to Subnets → Create Subnet.
- VPC ID: VPC-B
- Subnet name: Private-Subnet-B
- Availability Zone: No Preference
- IPv4 CIDR block: 20.0.0.0/24
- Click Create subnet.
3. Create Private Route Table
Go to Route tables → Create route table
- Name: PrivateRT
- VPC: VPC-B
After creating the route table, go to Subnet associations → select Private-Subnet-B and save.
(Note: We do not add an IGW here because this subnet will not have internet access.)
4. Launch Private EC2
Now we will launch an EC2 instance inside the private subnet without internet access.
- Go to EC2 → Launch instances
- Name: VPC-B_EC2
- AMI: Amazon Linux 2023 (kernel-6.1)
Network settings → Edit:
- VPC: VPC-B
- Subnet: varsayılan (Private_subnet_VPC-B)
- Auto-assign public IP: Disable
Firewall (SG): Create a new security group
- Name: Private_EC2_SG
- Description: Security group for private EC2
- Rule: SSH → Source: Anywhere
Click the Launch instance button and wait until the instance status becomes running.
✅ This instance will receive only a Private IP.
Section 3: Test Connectivity Between Two VPCs (Before Peering)
At this point, we have:
- VPC-A_EC2 (Public bastion) → Has a public IP, accessible from the internet.
- VPC-B_EC2 (Private) → Has only a private IP, no internet access.
👉 Our goal is to SSH into the private EC2 through the bastion using its private IP. But since peering is not yet configured, this connection will fail.
1. Connect to Bastion
First, connect to the bastion server (VPC-A_EC2) from your local machine via SSH:
chmod 400 ec2_ssh_key.pem
ssh -i ec2_ssh_key.pem ec2-user@<VPC-A_EC2_PUBLIC_IP>👉 This command logs you into the bastion.
2. Copy the Key to Bastion
Since we will use the same key to connect to the private EC2, we need to copy the .pem file from our local machine to the bastion:
scp -i ec2_ssh_key.pem ec2_ssh_key.pem ec2-user@3.89.19.3:/home/ec2-userAfter logging into the bastion via SSH, tighten the file permissions:
ssh -i ec2_ssh_key.pem ec2-user@3.89.19.3chmod 400 ec2_ssh_key.pem3. Try Connecting to the Private EC2
Now, from the bastion, attempt to SSH into the private EC2 using its private IP (for example 20.0.1.25):
ssh -i ec2_ssh_key.pem ec2-user@20.0.1.37
✅ At this point, the connection will fail, because there is no communication path (peering) between the two VPCs yet.
Section 4: Create VPC Peering and Update Route Tables
Now it’s time to build the bridge that will let the two VPCs communicate: the VPC Peering Connection.
In the AWS Console, go to VPC → Peering Connections → Create Peering Connection.
- Peering connection name tag: VPC-A_to_VPC-B
- VPC ID (Requester): VPC-A
- VPC ID (Accepter): VPC-B
- Region: Select the same region (e.g., us-east-1)
- Account: Leave as default
Click the Create Peering Connection button.
🔸 The status will initially be Pending Acceptance.
2. Accept the Peering Request
In the VPC → Peering Connections menu, select the connection you created.
Click Actions → Accept request.
The peering status should now be Active.
The status should be Active (refresh the page if you don’t see it).
Even though the peering between VPC-A and VPC-B is active, traffic will only flow once routes are added.
3. Update Route Tables
Although the peering connection is active, we need to update the route tables on both VPCs to allow traffic.
Go to Route tables → PublicRT-A → Edit routes.
PublicRT-A (VPC-A):
- Destination: 20.0.0.0/16
- Target: Peering connection (VPC-A_to_VPC-B)
Route tables→PrivateRT→edit routes
PrivateRT-B (VPC-B):
- Destination: 10.0.0.0/16
- Target: Peering connection (VPC-A_to_VPC-B)
🔑 This way, traffic between the two VPCs will be routed in both directions.
✅ You can now SSH from the bastion in VPC-A to the private EC2 in VPC-B:
ssh -i ec2_ssh_key.pem ec2-user@20.0.0.37Section 5: Cleanup
Every resource we create in a lab environment is a real AWS resource, and they may start incurring costs within minutes if left running. Especially EC2 instances and VPC Peering Connections can generate unnecessary charges if they remain active for a long time.
Therefore, after completing the lab, follow these steps to clean up your environment:
- Terminate EC2 Instances
- VPC-A_EC2 (public bastion)
- VPC-B_EC2 (private server)
- Delete Subnets
- Public-Subnet-A
- Private-Subnet-B
- Delete Route Tables
- PublicRT-A
- PrivateRT
- Delete Internet Gateway
- IGW-A
- Delete VPC Peering Connection
- VPC-A_to_VPC-B
- Delete VPCs
- VPC-A
- VPC-B
🎯 Conclusion
In this lab, we learned:
- How to establish secure communication between two VPCs using VPC Peering.
- How to test SSH connectivity from Public Bastion → Private EC2.
- That peering works only after updating both route tables.
- How to clean up resources to avoid unexpected costs.
💡 Takeaway: VPC Peering is simple and effective for small/medium-scale setups. However, if you need to connect many VPCs, a more centralized solution like Transit Gateway is recommended.
