Skip to main content
Current1mo ago

VPC Peering Connection

Use Case: Your ECS tasks in Account A need to connect to an RDS database in Account B over a private network — no public internet involved.


🧩 The Problem

You have two AWS accounts with separate VPCs:

ResourceAccountVPC CIDR (example)
ECS Cluster (Fargate/EC2)Account A — 11111111111110.0.0.0/16
RDS Database (PostgreSQL/MySQL)Account B — 22222222222210.1.0.0/16

By default, these two VPCs are completely isolated. Your ECS tasks cannot resolve or reach the RDS endpoint in the other account. You need a VPC Peering Connection to create a private network bridge between them.


🔑 What Is VPC Peering?

A VPC peering connection is a one-to-one networking link between two VPCs that routes traffic using private IP addresses. Resources in either VPC communicate as if they were in the same network.

Key characteristics:

  • Works across accounts and across regions (inter-region peering)

  • Traffic stays on the AWS backbone — never traverses the public internet

  • Inter-region traffic is encrypted automatically

  • No single point of failure or bandwidth bottleneck

  • No charge to create the peering connection (standard data transfer rates apply)

  • Data transfer within the same Availability Zone is free, even across accounts

Critical constraint: The two VPCs must not have overlapping CIDR blocks. If 10.0.0.0/16 and 10.0.0.0/16 overlap, VPC peering will not work. You would need to use AWS PrivateLink or re-address one of the VPCs.


🏗️ Architecture Overview

┌──────────────────────────────────────────────────────────────────────────┐
│ VPC Peering Connection │
│ (pcx-0abc123...) │
│ │
│ Account A (111111111111) Account B (222222222222) │
│ VPC: 10.0.0.0/16 VPC: 10.1.0.0/16 │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Private Subnet(s) │ │ Private Subnet(s) │ │
│ │ │ │ │ │
│ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │
│ │ │ ECS Tasks │ │◄────────►│ │ RDS Instance │ │ │
│ │ │ (Fargate) │ │ Private │ │ (PostgreSQL) │ │ │
│ │ └───────────────┘ │ Traffic │ └───────────────┘ │ │
│ │ │ │ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ Route Table: Route Table: │
│ 10.1.0.0/16 → pcx-0abc123 10.0.0.0/16 → pcx-0abc123 │
│ │
│ Security Group: Security Group: │
│ Outbound → 10.1.0.0/16:5432 Inbound ← 10.0.0.0/16:5432 │
└──────────────────────────────────────────────────────────────────────────┘


📋 Prerequisites Checklist

Before you begin, confirm the following:

  • You have admin or sufficient IAM permissions in both AWS accounts (VPC, EC2, RDS, IAM)

  • The CIDR blocks of the two VPCs do not overlap

  • You know the VPC ID in both accounts

  • You know the Account ID of the peer account

  • The RDS instance is in a private subnet (no public accessibility required)

  • Both VPCs have DNS hostnames and DNS resolution enabled


🚀 Step-by-Step Setup

Step 1 — Create the VPC Peering Connection (Account A — Requester)

  1. Open the VPC ConsolePeering ConnectionsCreate peering connection

  2. Fill in the details:

    • Name: ecs-to-rds-cross-account-peer
    • VPC ID (Requester): Select the VPC where your ECS cluster runs
    • Account: Choose Another account → enter Account B's ID (222222222222)
    • Region: Choose This Region (same region) or Another Region if cross-region
    • VPC ID (Accepter): Enter the VPC ID from Account B where RDS is hosted
  3. Click Create peering connection The connection status will show as Pending Acceptance.

AWS CLI equivalent:

aws ec2 create-vpc-peering-connection \
--vpc-id vpc-0aaa1111aaaa1111a \
--peer-vpc-id vpc-0bbb2222bbbb2222b \
--peer-owner-id 222222222222 \
--peer-region us-east-1 \
--tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=ecs-to-rds-cross-account-peer}]'


Step 2 — Accept the Peering Connection (Account B — Accepter)

  1. Log into Account B

  2. Open the VPC ConsolePeering Connections

  3. Find the pending peering request from Account A

  4. Select it → ActionsAccept request

  5. Confirm the acceptance The status should change to Active.

AWS CLI equivalent:

aws ec2 accept-vpc-peering-connection \
--vpc-peering-connection-id pcx-0abc123def456ghi7


Step 3 — Update Route Tables (Both Accounts)

This is the step people most commonly miss. Both VPCs need routes pointing to the peer VPC's CIDR through the peering connection.

Account A — ECS VPC Route Table

Add a route to the subnets where your ECS tasks run:

DestinationTarget
10.1.0.0/16 (Account B's VPC CIDR)pcx-0abc123def456ghi7 (Peering Connection)

Important: Update the route tables for every subnet where ECS tasks may launch. If you are using Fargate, update the route tables for the subnets specified in your ECS service's networkConfiguration.

aws ec2 create-route \
--route-table-id rtb-0aaa1111 \
--destination-cidr-block 10.1.0.0/16 \
--vpc-peering-connection-id pcx-0abc123def456ghi7

Account B — RDS VPC Route Table

Add a route to the subnets where RDS is deployed:

DestinationTarget
10.0.0.0/16 (Account A's VPC CIDR)pcx-0abc123def456ghi7 (Peering Connection)

Update the route tables associated with all subnets in the RDS subnet group.

aws ec2 create-route \
--route-table-id rtb-0bbb2222 \
--destination-cidr-block 10.0.0.0/16 \
--vpc-peering-connection-id pcx-0abc123def456ghi7


Step 4 — Configure Security Groups

Account B — RDS Security Group (Inbound)

Allow traffic from Account A's ECS VPC on the database port:

TypeProtocolPortSourceDescription
Custom TCPTCP5432 (PostgreSQL) or 3306 (MySQL)10.0.0.0/16Allow ECS from Account A

Note: For same-region, same-account peering you can reference security groups directly. For cross-account peering, you must use the CIDR block as the source — security group references across accounts are only supported for peering connections within the same region.

aws ec2 authorize-security-group-ingress \
--group-id sg-0bbb2222rds \
--protocol tcp \
--port 5432 \
--cidr 10.0.0.0/16

Account A — ECS Task Security Group (Outbound)

If your ECS task security group restricts outbound traffic (not the default 0.0.0.0/0 allow-all), add an outbound rule:

TypeProtocolPortDestinationDescription
Custom TCPTCP543210.1.0.0/16Allow outbound to RDS in Account B

Step 5 — Enable DNS Resolution for VPC Peering

This is a critical step that is easy to overlook. By default, when your ECS task resolves the RDS endpoint hostname (e.g., mydb.abc123.us-east-1.rds.amazonaws.com), it may resolve to the public IP instead of the private IP. Enabling DNS resolution on the peering connection ensures the RDS endpoint resolves to its private IP from the peer VPC.

  1. In Account A (requester): Go to VPC ConsolePeering Connections → select the connection → ActionsEdit DNS settings

    • Enable: Requester DNS resolution"Allow accepter VPC to resolve DNS of requester VPC hosts to private IP"
  2. In Account B (accepter): Same steps but enable Accepter DNS resolution"Allow requester VPC to resolve DNS of accepter VPC hosts to private IP" Both sides must enable their respective setting independently.

AWS CLI:


# Run from Account A
aws ec2 modify-vpc-peering-connection-options \
--vpc-peering-connection-id pcx-0abc123def456ghi7 \
--requester-peering-connection-options AllowDnsResolutionFromRemoteVpc=true

# Run from Account B
aws ec2 modify-vpc-peering-connection-options \
--vpc-peering-connection-id pcx-0abc123def456ghi7 \
--accepter-peering-connection-options AllowDnsResolutionFromRemoteVpc=true

Also ensure both VPCs have these settings enabled:

  • Enable DNS hostnamestrue

  • Enable DNS resolutiontrue


Step 6 — Verify Network ACLs

Network ACLs (NACLs) are stateless and evaluated before security groups. Make sure the NACLs on both subnets allow:

Account A subnets (ECS):

  • Outbound: Allow TCP to 10.1.0.0/16 on port 5432

  • Inbound: Allow TCP from 10.1.0.0/16 on ephemeral ports (1024-65535) — for return traffic

Account B subnets (RDS):

  • Inbound: Allow TCP from 10.0.0.0/16 on port 5432

  • Outbound: Allow TCP to 10.0.0.0/16 on ephemeral ports (1024-65535) — for return traffic If you are using the default NACLs (allow all), no changes are needed.


Step 7 — Configure ECS Task to Connect

In your ECS task definition, pass the RDS endpoint as an environment variable or use AWS Secrets Manager for credentials.

Task definition snippet (Fargate, awsvpc mode):

{
"containerDefinitions": [
{
"name": "my-app",
"image": "my-app:latest",
"environment": [
{
"name": "DB_HOST",
"value": "mydb.abc123.us-east-1.rds.amazonaws.com"
},
{
"name": "DB_PORT",
"value": "5432"
}
],
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:222222222222:secret:mydb-creds"
}
]
}
],
"networkMode": "awsvpc"
}

For cross-account Secrets Manager access: The secret in Account B must have a resource policy allowing Account A's ECS task execution role to read it. Alternatively, store a copy of the credentials in Account A's Secrets Manager.


✅ Verification & Testing

Test from within an ECS task or EC2 in Account A


# DNS resolution check — should return a private IP in 10.1.x.x range
dig mydb.abc123.us-east-1.rds.amazonaws.com +short

# Connectivity check
telnet mydb.abc123.us-east-1.rds.amazonaws.com 5432

# Or use nc (netcat)
nc -zv mydb.abc123.us-east-1.rds.amazonaws.com 5432

What to check if it fails

SymptomLikely CauseFix
DNS resolves to public IPDNS resolution not enabled on peering connectionEnable requester/accepter DNS resolution (Step 5)
Connection times outMissing route in route tableAdd route to peer CIDR via peering connection (Step 3)
Connection refusedSecurity group blockingAdd inbound rule on RDS SG for Account A CIDR (Step 4)
Peering shows "Pending"Not accepted yetAccept in Account B (Step 2)
Peering shows "Failed"Overlapping CIDRsRe-address one VPC or use PrivateLink instead
Intermittent failuresRoute table only updated on some subnetsUpdate ALL subnets where ECS tasks or RDS instances reside
Works from EC2 but not ECSWrong security group on ECS taskIn awsvpc mode, the task security group governs access, not the instance SG

🔄 Terraform Example


# ─── Account A (ECS) ───

resource "aws_vpc_peering_connection" "ecs_to_rds" {
vpc_id = aws_vpc.ecs_vpc.id
peer_vpc_id = "vpc-0bbb2222bbbb2222b" # Account B's VPC ID
peer_owner_id = "222222222222" # Account B's Account ID
peer_region = "us-east-1"

tags = {
Name = "ecs-to-rds-cross-account"
}
}

# ─── Account B (RDS) ───

resource "aws_vpc_peering_connection_accepter" "rds_accept" {
provider = aws.account_b
vpc_peering_connection_id = aws_vpc_peering_connection.ecs_to_rds.id
auto_accept = true
}

# ─── DNS Resolution (both sides) ───

resource "aws_vpc_peering_connection_options" "requester_dns" {
vpc_peering_connection_id = aws_vpc_peering_connection.ecs_to_rds.id

requester {
allow_remote_vpc_dns_resolution = true
}
}

resource "aws_vpc_peering_connection_options" "accepter_dns" {
provider = aws.account_b
vpc_peering_connection_id = aws_vpc_peering_connection.ecs_to_rds.id

accepter {
allow_remote_vpc_dns_resolution = true
}
}

# ─── Route Tables ───

resource "aws_route" "ecs_to_rds_route" {
route_table_id = aws_route_table.ecs_private.id
destination_cidr_block = "10.1.0.0/16"
vpc_peering_connection_id = aws_vpc_peering_connection.ecs_to_rds.id
}

resource "aws_route" "rds_to_ecs_route" {
provider = aws.account_b
route_table_id = aws_route_table.rds_private.id
destination_cidr_block = "10.0.0.0/16"
vpc_peering_connection_id = aws_vpc_peering_connection.ecs_to_rds.id
}

# ─── Security Groups ───

resource "aws_security_group_rule" "rds_allow_ecs" {
provider = aws.account_b
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
security_group_id = aws_security_group.rds_sg.id
description = "Allow PostgreSQL from ECS in Account A"
}


⚖️ VPC Peering vs. Alternatives

FeatureVPC PeeringAWS PrivateLink + NLBTransit GatewayVPC Lattice
ComplexityLowMedium-HighMediumMedium
CostFree to create; data transfer chargesNLB + PrivateLink hourly + dataTGW hourly + dataPer-request pricing
Overlapping CIDRsNot supportedSupportedNot supportedSupported
Scope of exposureEntire VPC-to-VPCOnly the specific serviceEntire VPC-to-VPCOnly the specific resource
Scalability1:1 connections (max 125 per VPC)Many consumers per endpointHub-and-spoke (thousands of VPCs)Service mesh approach
Cross-regionSupportedRequires peering for cross-regionSupported (with peering)Supported
Best forSimple, low-cost 1:1 connectionsGranular access, overlapping CIDRsLarge orgs, many VPCsModern service-to-service mesh

When to choose VPC Peering: You have a small number of VPCs with non-overlapping CIDRs and want the simplest, cheapest option for direct connectivity.

When NOT to choose VPC Peering: Your CIDRs overlap, you need fine-grained service-level access control, or you have dozens of VPCs to interconnect.


⚠️ Common Pitfalls & Gotchas

  1. Forgetting DNS resolution — The #1 issue. Without enabling DNS resolution on the peering connection, the RDS endpoint resolves to its public IP and traffic goes out to the internet (and gets blocked by security groups). Always enable DNS resolution on both sides.

  2. Route tables not updated on all subnets — If your ECS tasks can launch in multiple subnets, every associated route table needs the peering route. Miss one, and tasks in that subnet can't reach RDS.

  3. ECS awsvpc mode security group — In awsvpc network mode (required for Fargate), the task-level security group controls network access, not the EC2 host security group. Make sure the task SG allows outbound to the RDS port.

  4. RDS IP changes after modifications — If you upgrade, resize, or failover an RDS instance, its private IP can change. Always use the DNS endpoint, never hardcode IPs. With DNS resolution enabled on peering, the endpoint will correctly resolve to the new private IP.

  5. Transitive peering is not supported — If VPC A peers with VPC B, and VPC B peers with VPC C, VPC A cannot reach VPC C through VPC B. Each pair needs its own peering connection (or use Transit Gateway).

  6. Cross-account security group references — You can reference a peer VPC's security group in rules only for same-region peering. For cross-region peering, use CIDR blocks.

  7. Secrets Manager cross-account access — If your DB credentials are stored in Account B's Secrets Manager, you need a resource policy on the secret granting access to Account A's ECS task role, plus a KMS key policy if using a CMK.


📎 Useful AWS CLI Commands


# List all peering connections
aws ec2 describe-vpc-peering-connections

# Check peering connection status
aws ec2 describe-vpc-peering-connections \
--vpc-peering-connection-ids pcx-0abc123def456ghi7 \
--query 'VpcPeeringConnections[0].Status'

# Verify route tables
aws ec2 describe-route-tables \
--route-table-ids rtb-0aaa1111 \
--query 'RouteTables[0].Routes'

# Check security group rules
aws ec2 describe-security-groups \
--group-ids sg-0bbb2222rds \
--query 'SecurityGroups[0].IpPermissions'

# Delete a peering connection
aws ec2 delete-vpc-peering-connection \
--vpc-peering-connection-id pcx-0abc123def456ghi7


📚 References


Last updated: March 2026

Related Articles