DEV Community

Cover image for AWS VPC Peering Using Terraform
Adarsh Gupta
Adarsh Gupta

Posted on

AWS VPC Peering Using Terraform

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.primary manages resources in the primary region
  • aws.secondary manages 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}"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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

  1. EC2 instance sends traffic to the destination private IP
  2. Subnet-associated route table matches the peered CIDR
  3. Traffic is routed through the VPC peering connection
  4. Security groups allow the request
  5. 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.


Embedded Video Tutorial


@piyushsachdeva


Top comments (0)