Beyond a handful of VPCs, peering meshes become unmanageable; Transit Gateway is the standard hub for enterprise AWS networks.
VPC peering connects two VPCs privately but is non-transitive: if A peers with B and B with C, A cannot reach C. With dozens of VPCs a full mesh needs n(n-1)/2 peerings.
A Transit Gateway (TGW) is a regional, highly available router. You create attachments: VPCs (via one subnet per AZ), Site-to-Site VPNs, Direct Connect gateways, peering to TGWs in other Regions, and Connect attachments for SD-WAN appliances. Share one TGW to all accounts through AWS RAM.
Routing has two separate ideas, and confusing them is the classic mistake:
- Association: each attachment is associated with exactly one TGW route table, which is used to look up where its traffic goes.
- Propagation: an attachment can propagate its CIDRs into any number of route tables, so others learn how to reach it.
Multiple route tables give segmentation: prod and dev VPCs associate with different tables, both learn routes to a shared-services VPC, but not to each other. Static routes and blackhole routes add control. The VPCs' own subnet route tables still need routes pointing to the TGW.
Common patterns: centralised egress (one NAT VPC for the organisation) and centralised inspection with Network Firewall or appliances, where appliance mode keeps both directions of a flow in the same AZ.
Costs are per attachment-hour plus per GB processed, so heavy VPC-to-VPC flows may still justify direct peering. CIDRs must not overlap; manage them with VPC IPAM. For multi-Region, policy-driven networks, AWS Cloud WAN builds on the same ideas.
Example
TGW=$(aws ec2 create-transit-gateway --description core \
--options DefaultRouteTableAssociation=disable,DefaultRouteTablePropagation=disable \
--query TransitGateway.TransitGatewayId --output text)
ATT_PROD=$(aws ec2 create-transit-gateway-vpc-attachment --transit-gateway-id "$TGW" \
--vpc-id vpc-prod --subnet-ids subnet-tgw-a subnet-tgw-b \
--query TransitGatewayVpcAttachment.TransitGatewayAttachmentId --output text)
RT_PROD=$(aws ec2 create-transit-gateway-route-table --transit-gateway-id "$TGW" \
--query TransitGatewayRouteTable.TransitGatewayRouteTableId --output text)
# Prod looks up routes in RT_PROD, which learns only shared services
# (ATT_SHARED = the shared-services VPC attachment, created the same way)
aws ec2 associate-transit-gateway-route-table \
--transit-gateway-route-table-id "$RT_PROD" --transit-gateway-attachment-id "$ATT_PROD"
aws ec2 enable-transit-gateway-route-table-propagation \
--transit-gateway-route-table-id "$RT_PROD" --transit-gateway-attachment-id "$ATT_SHARED"
# The VPC side still needs a route to the TGW
aws ec2 create-route --route-table-id rtb-prod-priv-a \
--destination-cidr-block 10.0.0.0/8 --transit-gateway-id "$TGW"Remember
- Peering is non-transitive; TGW is a transitive regional hub
- Association picks an attachment's lookup table; propagation advertises its routes
- Multiple TGW route tables give segmentation between environments
- Share via RAM, avoid overlapping CIDRs, and watch per-GB processing costs
Watch out for
- Forgetting the VPC subnet route to the TGW, so traffic never enters the hub even though TGW routes look right.