Transit Gateway Peering Across Regions
A Transit Gateway is regional. To build a global network - VPCs in us-east-1 reaching VPCs in eu-west-1 privately - you peer the Transit Gateway in each region.
Search across all documentation pages
A Transit Gateway is regional. To build a global network - VPCs in us-east-1 reaching VPCs in eu-west-1 privately - you peer the Transit Gateway in each region.
This page shows the SDK flow: request a peering attachment from one region, accept it from the other, then add the static routes that make traffic actually cross. Peering is the glue of a multi-region topology, and the routing model has one surprise worth learning up front.
CreateTransitGatewayPeeringAttachment requests a peering from the local Transit Gateway to a peer in another region. You name the peer's Transit Gateway id, account, and region.
# --- Python (boto3) --- run against the requester region (us-east-1)
import boto3
ec2_east = boto3.client("ec2", region_name="us-east-1")
peer = ec2_east.create_transit_gateway_peering_attachment(
TransitGatewayId="tgw-0east111",
PeerTransitGatewayId="tgw-0west222",
PeerAccountId="111122223333",
PeerRegion="eu-west-1",
)
pa_id = peer["TransitGatewayPeeringAttachment"]["TransitGatewayAttachmentId"]
print(pa_id, peer["TransitGatewayPeeringAttachment"]["State"]) # pendingAcceptance// --- TypeScript (AWS SDK v3) --- run against the requester region (us-east-1)
import { EC2Client, CreateTransitGatewayPeeringAttachmentCommand } from "@aws-sdk/client-ec2";
const ec2East = new EC2Client({ region: "us-east-1" });
const peer = await ec2East.send(new CreateTransitGatewayPeeringAttachmentCommand({
TransitGatewayId: "tgw-0east111",
PeerTransitGatewayId: "tgw-0west222",
PeerAccountId: "111122223333",
PeerRegion: "eu-west-1",
}));
const paId = peer.TransitGatewayPeeringAttachment?.TransitGatewayAttachmentId;
console.log(paId, peer.TransitGatewayPeeringAttachment?.State); // pendingAcceptanceThe attachment starts in pendingAcceptance. Even peering two Transit Gateways you own in the same account, the peer region must still accept - the request and the acceptance live in different regions.
Accept the peering from the peer region's client, then add the static routes. Cross-region peering does not propagate routes, so you add them by hand.
# --- Python (boto3) --- accept from the peer region (eu-west-1)
import boto3
ec2_west = boto3.client("ec2", region_name="eu-west-1")
ec2_west.accept_transit_gateway_peering_attachment(
TransitGatewayAttachmentId="tgw-attach-0peer",
)
# static routes on BOTH regions' route tables, pointing at the peering attachment
ec2_east = boto3.client("ec2", region_name="us-east-1")
ec2_east.create_transit_gateway_route(
TransitGatewayRouteTableId="tgw-rtb-east",
DestinationCidrBlock="10.20.0.0/16", # the west CIDR
TransitGatewayAttachmentId="tgw-attach-0peer",
)
ec2_west.create_transit_gateway_route(
TransitGatewayRouteTableId="tgw-rtb-west",
DestinationCidrBlock="10.10.0.0/16", # the east CIDR
TransitGatewayAttachmentId="tgw-attach-0peer",
)// --- TypeScript (AWS SDK v3) --- accept from the peer region (eu-west-1)
import {
EC2Client, AcceptTransitGatewayPeeringAttachmentCommand, CreateTransitGatewayRouteCommand,
} from "@aws-sdk/client-ec2";
const ec2West = new EC2Client({ region: "eu-west-1" });
await ec2West.send(new AcceptTransitGatewayPeeringAttachmentCommand({
TransitGatewayAttachmentId: "tgw-attach-0peer",
}));
// static routes on BOTH regions' route tables, pointing at the peering attachment
const ec2East = new EC2Client({ region: "us-east-1" });
await ec2East.send(new CreateTransitGatewayRouteCommand({
TransitGatewayRouteTableId: "tgw-rtb-east",
DestinationCidrBlock: "10.20.0.0/16", // the west CIDR
TransitGatewayAttachmentId: "tgw-attach-0peer",
}));
await ec2West.send(new CreateTransitGatewayRouteCommand({
TransitGatewayRouteTableId: "tgw-rtb-west",
DestinationCidrBlock: "10.10.0.0/16", // the east CIDR
TransitGatewayAttachmentId: "tgw-attach-0peer",
}));Two things make or break this. First, the accept call runs against the peer region's client - point your SDK client at eu-west-1, not us-east-1. Second, both regions need a static route to the other's CIDR through the peering attachment, or traffic goes one way and replies black-hole.
Within one region, an attachment can propagate its routes into a Transit Gateway route table automatically. Cross-region peering attachments cannot - route propagation is not supported over a peering attachment, so every cross-region route is static. This is the single most common stumbling block: the peering shows available, but nothing routes because no one added the static entries.
Plan for it. Keep a clear list of which CIDRs live in which region, and script the CreateTransitGatewayRoute calls on both sides as part of the same automation that creates the peering.
Peering traffic rides the AWS global backbone, not the public internet, and it is encrypted in transit. You do not manage tunnels, pre-shared keys, or BGP for a peering the way you do for a VPN. That makes peering the natural choice for region-to-region hub connectivity where both ends are AWS Transit Gateways.
Latency is still physics - us-east-1 to ap-southeast-2 is a long way - but the path is private and consistent, which is exactly what a global application backbone wants.
After adding routes, confirm them with SearchTransitGatewayRoutes rather than assuming. It filters the routes in a table so you can check the cross-region prefix resolved to the peering attachment and is active.
# --- Python (boto3) ---
resp = ec2_east.search_transit_gateway_routes(
TransitGatewayRouteTableId="tgw-rtb-east",
Filters=[{"Name": "route-search.subnet-of-match", "Values": ["10.20.0.0/16"]}],
)
for r in resp["Routes"]:
print(r["DestinationCidrBlock"], r["State"], r["Type"])// --- TypeScript (AWS SDK v3) ---
import { SearchTransitGatewayRoutesCommand } from "@aws-sdk/client-ec2";
const resp = await ec2East.send(new SearchTransitGatewayRoutesCommand({
TransitGatewayRouteTableId: "tgw-rtb-east",
Filters: [{ Name: "route-search.subnet-of-match", Values: ["10.20.0.0/16"] }],
}));
for (const r of resp.Routes ?? []) {
console.log(r.DestinationCidrBlock, r.State, r.Type);
}A State of active and Type of static on the cross-region prefix confirms the route is live. And remember the VPC-side routing in each region still points its subnets at the local Transit Gateway.
AcceptTransitGatewayPeeringAttachment must run against the peer region's client. Pointing it at the requester region fails.pendingAcceptance until the peer region accepts.Use Transit Gateway peering to stitch regional hubs into one private global backbone; drop to inter-region VPC peering for a one-off VPC pair; move to Cloud WAN when the static-route bookkeeping across regions stops being manageable.
No. Route propagation is not supported over a peering attachment. Every cross-region route must be added statically with CreateTransitGatewayRoute on both regions' route tables.
The peer (accepter) region. Point your SDK client at that region and call AcceptTransitGatewayPeeringAttachment. Running it against the requester region does not work.
Yes. A cross-region peering attachment stays in pendingAcceptance until the peer region accepts, regardless of whether both Transit Gateways are in the same account.
Yes. Transit Gateway peering runs over the AWS global backbone and is encrypted in transit. You do not manage tunnels or keys as you would with a VPN.
Almost always because no static routes were added. Peering does not propagate, so both regions need explicit routes to the other's CIDR through the peering attachment.
Yes. Supply the peer's PeerAccountId (and PeerRegion) on the request, and the peer account accepts it in its region. Routing is still static on both sides.
Use SearchTransitGatewayRoutes on the route table and check the cross-region prefix shows State active and Type static. Also confirm the VPC subnet routes point at the local Transit Gateway.
It gives a private, consistent path over the AWS backbone rather than the public internet, but physical distance still sets the floor. It improves consistency and privacy, not the speed of light.
Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 23, 2026