Introduction
As cloud architectures grow, it becomes common to split workloads across multiple Virtual Private Clouds (VPCs). These VPCs may exist in different AWS regions for reasons such as fault tolerance, regulatory compliance, or performance optimization. To enable secure communication between such isolated networks, AWS provides VPC Peering.
This blog walks through a complete Terraform-based implementation of cross-region VPC peering, explaining each resource in detail and showing how two VPCs can communicate using private IP addresses without relying on the public internet.
What Is VPC Peering?
VPC peering is a networking connection between two VPCs that allows traffic to be routed privately using AWS’s internal network. Once peered, resources in both VPCs can communicate as if they were part of the same network, provided routing and security rules are configured correctly.
Key characteristics:
- Traffic stays on the AWS backbone
- No internet gateway, VPN, or NAT is required for inter-VPC communication
- CIDR blocks must not overlap
- Peering is non-transitive
Architecture Overview
This implementation creates:
- Two VPCs in different AWS regions (Primary and Secondary)
- One public subnet in each VPC
- Internet gateways for outbound connectivity
- Custom route tables with peering routes
- A cross-region VPC peering connection
- Security groups allowing controlled inter-VPC traffic
- One EC2 instance in each VPC for testing connectivity
Provider and Region Strategy
Terraform does not support dynamic regions inside a single provider block. To solve this, provider aliases are used.
-
aws.primarymanages resources in the primary region -
aws.secondarymanages resources in the secondary region
This approach enables clean multi-region deployments within a single Terraform configuration.
Creating the VPCs
Two VPCs are created with DNS support and hostnames enabled.
resource "aws_vpc" "primary" {
cidr_block = var.primary_vpc_cidr
provider = aws.primary
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "Primary-VPC-${var.primary}"
}
}
DNS support is critical for resolving private hostnames across peered VPCs, especially when services rely on DNS instead of hardcoded IPs.
The secondary VPC follows the same structure but uses the secondary provider and CIDR block.
Subnet Configuration
Each VPC contains one public subnet:
resource "aws_subnet" "primary_subnet" {
provider = aws.primary
vpc_id = aws_vpc.primary.id
cidr_block = var.primary_vpc_cidr
availability_zone = data.aws_availability_zones.primary.names[0]
map_public_ip_on_launch = true
}
The subnet is configured to automatically assign public IPs, enabling direct SSH access to EC2 instances for testing purposes.
Internet Gateways
An Internet Gateway is attached to each VPC to allow outbound internet access:
resource "aws_internet_gateway" "primary_igw" {
provider = aws.primary
vpc_id = aws_vpc.primary.id
}
This is necessary for:
- Software installation
- SSH access
- Package updates on EC2 instances
Route Tables and Associations
Custom route tables are created to control traffic flow.
resource "aws_route_table" "primary_rt" {
provider = aws.primary
vpc_id = aws_vpc.primary.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.primary_igw.id
}
}
Each subnet is explicitly associated with its route table:
resource "aws_route_table_association" "asso_primary" {
provider = aws.primary
subnet_id = aws_subnet.primary_subnet.id
route_table_id = aws_route_table.primary_rt.id
}
Associating route tables at the subnet level ensures consistent routing behavior for all instances within that subnet.
VPC Peering Connection
The peering connection is initiated from the primary VPC:
resource "aws_vpc_peering_connection" "primary_to_secondary" {
provider = aws.primary
vpc_id = aws_vpc.primary.id
peer_vpc_id = aws_vpc.secondary.id
peer_region = var.secondary
auto_accept = false
}
Since this is a cross-region peering, the connection must be explicitly accepted by the secondary VPC.
resource "aws_vpc_peering_connection_accepter" "secondary_accepter" {
provider = aws.secondary
vpc_peering_connection_id = aws_vpc_peering_connection.primary_to_secondary.id
auto_accept = true
}
This two-step process reflects AWS’s security model for cross-region peering.
Routing for VPC Peering
Peering alone does not enable traffic flow. Routes must be added in both VPCs.
Primary route table:
resource "aws_route" "primary_to_secondary" {
provider = aws.primary
route_table_id = aws_route_table.primary_rt.id
destination_cidr_block = var.secondary_vpc_cidr
vpc_peering_connection_id = aws_vpc_peering_connection.primary_to_secondary.id
}
Secondary route table:
resource "aws_route" "secondary_to_primary" {
provider = aws.secondary
route_table_id = aws_route_table.secondary_rt.id
destination_cidr_block = var.primary_vpc_cidr
vpc_peering_connection_id = aws_vpc_peering_connection.primary_to_secondary.id
}
These routes instruct AWS to forward inter-VPC traffic through the peering connection.
Security Groups
Security groups enforce network-level access control.
Primary VPC security group allows:
- SSH from anywhere
- ICMP from the secondary VPC
- All TCP traffic from the secondary VPC
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = [var.secondary_vpc_cidr]
}
The secondary security group mirrors this configuration, allowing symmetric communication.
EC2 Instances for Validation
One EC2 instance is launched in each VPC:
resource "aws_instance" "primary_instance" {
provider = aws.primary
subnet_id = aws_subnet.primary_subnet.id
instance_type = var.instance_type
}
User data scripts install a web server and expose simple HTTP endpoints, making it easy to validate connectivity using curl or ping.
Traffic Flow Summary
- EC2 instance sends traffic to the destination private IP
- Subnet-associated route table matches the peered CIDR
- Traffic is routed through the VPC peering connection
- Security groups allow the request
- Response returns via the same private path
No traffic traverses the public internet during inter-VPC communication.
Key Takeaways
- VPC peering provides secure, low-latency private connectivity
- Route tables are mandatory for peering traffic
- Security groups must explicitly allow cross-VPC CIDRs
- Provider aliases enable clean multi-region Terraform deployments
- DNS support is essential for scalable architectures
Conclusion
This Terraform implementation demonstrates a production-grade approach to AWS VPC Peering across regions. By combining proper routing, security controls, and provider aliasing, you can build scalable, secure multi-region architectures without introducing unnecessary complexity.
This setup forms a strong foundation for extending into private subnets, NAT gateways, monitoring, and more advanced networking patterns such as Transit Gateway.
Top comments (0)