Skip to content

VPC peering not working: the route, security group, and DNS checklist

An active VPC peering connection that passes no traffic almost always has missing routes on one side, security groups that block the peer CIDR, or DNS resolution left off. Work this checklist in order.

A VPC peering connection showing Active proves only that two VPCs agreed to peer — it moves zero packets by itself. When instances across a peering cannot talk, the cause is almost always one of four things, in this order of likelihood: routes to the peer CIDR are missing from one or both sides’ subnet route tables, security groups do not allow the peer’s addresses, DNS resolution over the peering was never enabled so clients connect to public IPs instead, or the traffic was never going to work because peering is not transitive. Work the checklist in that order and you will find it.

What’s happening

Peering is a routing arrangement, not a tunnel that captures traffic automatically. For a packet to cross, the source subnet’s route table needs a route for the peer VPC’s CIDR targeting the peering connection (pcx-...), and the destination subnet’s table needs the mirror route back — replies are routed independently, so a one-sided setup produces silent timeouts, not errors. Then the destination’s security group has to allow the source. Across a same-region peering you can reference the peer’s security group ID directly; across regions you must allow the peer CIDR instead, and this asymmetry catches teams who templated their rules on SG references.

DNS is the sneaky third layer. By default, if an instance in VPC A resolves the public DNS name of an instance in VPC B, it gets the public IP — and the connection leaves through the internet path or dies at a security group that only allows the peer CIDR. Each side of the peering has a DNS resolution option that makes public hostnames resolve to private IPs for the peer; it ships disabled and must be enabled on both sides for both directions to behave. Note this applies to EC2 public DNS names; private hosted zones are separate — they need explicit VPC association, peering or not.

Finally, the structural limits: peering does not carry transitive traffic (A-B and B-C does not give A-C), does not work at all with overlapping CIDRs, and offers no way to reference a third VPC’s routes. If your problem is one of those, no checklist fixes it — the design does; see VPC peering vs PrivateLink for when each fits.

What to check first

  • Peering state is active — not pending-acceptance (the accepter never clicked) and not failed (usually overlapping CIDRs).
  • Routes on both sides, in the route tables of the specific subnets involved, not just somewhere in the VPC.
  • Security group on the destination allows the source — by CIDR for cross-region, SG reference allowed same-region only.
  • NACLs if the subnets use non-default ones — both directions, including ephemeral return ports.
  • What IP the client actually connects to — private peer IP or a public one from DNS.

How to verify

# Peering connection state and both CIDRs
aws ec2 describe-vpc-peering-connections \
  --query 'VpcPeeringConnections[].{Id:VpcPeeringConnectionId,Status:Status.Code,ReqCidr:RequesterVpcInfo.CidrBlock,AccCidr:AccepterVpcInfo.CidrBlock}'

# Routes toward the peering in the source subnet's table
aws ec2 describe-route-tables \
  --filters "Name=association.subnet-id,Values=subnet-0abc123" \
  --query 'RouteTables[].Routes[?VpcPeeringConnectionId!=`null`]'

# DNS resolution options on the peering
aws ec2 describe-vpc-peering-connections \
  --vpc-peering-connection-ids pcx-0abc123 \
  --query 'VpcPeeringConnections[].{Req:RequesterVpcInfo.PeeringOptions,Acc:AccepterVpcInfo.PeeringOptions}'

From the source instance, split routing from filtering:

# Does the name resolve to the private peer IP?
dig ec2-3-98-x-x.ca-central-1.compute.amazonaws.com +short
# Does the port answer on the private IP?
nc -vz -w 5 10.1.20.15 5432

If nc times out with correct routes, suspect security groups; if it is refused, the network path works and the service is the problem. Reachability Analyzer (VPC > Reachability Analyzer) will name the blocking component across a peering in one run. The peering behaviour reference is in the Amazon VPC documentation.

The fix

  1. Add the routes, both sides, right tables. In VPC A’s subnet route tables: peer CIDR -> pcx-.... Mirror in VPC B. If private route tables are per-AZ, update every one that hosts an involved subnet.
  2. Open the security group for the peer. Cross-region: add the peer CIDR (or specific subnets) on the service port. Same-region: an SG reference is cleaner and survives re-IPs. Keep the rule tight — peer subnet, not 10.0.0.0/8.
  3. Enable DNS resolution over the peering on both sides. aws ec2 modify-vpc-peering-connection-options --vpc-peering-connection-id pcx-0abc123 --requester-peering-connection-options AllowDnsResolutionFromRemoteVpc=true and the matching --accepter-peering-connection-options from the accepter’s account. Both VPCs also need enableDnsSupport and enableDnsHostnames on.
  4. Associate private hosted zones with the peer VPC if internal names must resolve across — peering alone never shares private zones.
  5. Fix NACLs last. If custom NACLs exist, allow the peer CIDR inbound on the service port and outbound on ephemeral ports (1024-65535) — stateless means both directions, on both sides.
  6. If the requirement is transitive or overlapping, stop patching: overlapping CIDRs require re-addressing or NAT tricks that rarely age well, and hub topologies belong on a Transit Gateway.

When this is the wrong move

If you only need to expose one service — a database, an internal API — to another VPC or another organization, full-mesh network reachability is more than the requirement calls for, and PrivateLink is the safer shape: one-way, service-only, no route table coupling. And once you are approaching double-digit peerings with hand-maintained mirror routes, the operational risk is the mesh itself, not any single connection.

Common mistakes

  • Declaring victory when the peering shows active without ever adding routes.
  • Adding routes to the VPC’s main route table while the workload subnets use their own tables.
  • Using a security group reference across regions, where it is not supported, and reading the silence as a routing bug.
  • Testing by pinging a public DNS name that still resolves to the public IP because peering DNS resolution was left off.

Prevention

  • Create peerings through IaC modules that emit both sides’ routes, SG rules, and DNS options together — never a console-made peering with hand-added routes.
  • Standardize non-overlapping CIDR allocation across accounts before the first peering, not after the third.
  • Run Reachability Analyzer as an acceptance test whenever a peering is created or changed.
  • Document each peering’s purpose and owning team; orphaned peerings are the ones nobody dares delete.
  • Alarm on blackhole routes — a deleted peering leaves its routes behind, silently dropping traffic.

An active peering with no routes is a handshake, not a network. Routes both ways, security groups for the peer, DNS resolution switched on — in that order, and most “peering is broken” tickets close on step one. If nobody on your team truly owns the AWS environment end to end, that is the root of most of these problems — Stack Harbor runs AWS accounts daily under AWS Operations, and you can book an AWS review.