
Transit Gateway の 1:1 NAT で CIDR が重複した 2 つの VPC をつないでみた
はじめに
AWS Transit Gateway と AWS Cloud WAN が 1:1 NAT に対応しました。
1:1 NAT は、アタッチメントに変換ルールを設定する機能です。重複した CIDR のネットワークを、リナンバリングや NAT 用の機器なしでつなげます。
この記事では、Transit Gateway の VPC アタッチメントで、同じ 10.0.0.0/16 の 2 つの VPC(A と B)をつないだ結果を紹介します。
検証内容
構成
リージョンは ap-northeast-1、AWS CLI は 2.38.5 です。単一アカウントで、各 VPC の 1 つの AZ に、インスタンスを 1 台ずつ置きました。
2 つの VPC は、CIDR(10.0.0.0/16)、サブネット(10.0.1.0/24)、インスタンスの IP(10.0.1.10)がすべて同じです。NAT ルールは各アタッチメントに設定し、A は 100.64.0.0/16、B は 100.65.0.0/16 に変換します。変換先は 100.64.0.0/10 の範囲から、各 VPC の CIDR と重ならないものを選びました。相手からは、A が 100.64.1.10、B が 100.65.1.10 として見えます。図の矢印は、A から B へのリクエストの向きです。

Transit Gateway は 1 つで、既定のルートテーブルに両方のアタッチメントを関連付け、伝播も有効にしています。インターネット経路と VPC エンドポイントは置いていません。
B のインスタンスはポート 8080 で HTTP サーバーを動かし、アクセスログを残します。A のインスタンスは、B の NAT アドレス 100.65.1.10 へ curl を繰り返します。curl は 3 秒でタイムアウトし、終わるたびに 5 秒待つので、成功時は約 5 秒、失敗が続くときは約 8 秒の間隔になります。ログはコンソール出力から取得しました。
ドキュメントによると、変換ではホスト部がそのまま引き継がれ、ポートは変わらず、対象は IPv4 のみです。
疎通確認に使った user data とログの取得コマンド
VPC-A
#!/bin/bash
# VPC-A: B の NAT アドレス (100.65.1.10) へ 5 秒ごとに curl し、結果を時刻付きでコンソールへ書く
setsid nohup bash -c '
while true; do
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
code=$(curl -s -m 3 -o /dev/null -w "%{http_code}" http://100.65.1.10:8080/)
echo "A-CURL $ts target=100.65.1.10 http=$code exit=$?" > /dev/console
sleep 5
done' > /dev/null 2>&1 < /dev/null &
VPC-B
#!/bin/bash
# VPC-B: 8080 で HTTP サーバーを起動し、アクセスログ(送信元アドレス入り)をコンソールへ書く
setsid nohup bash -c 'echo "B-SERVER start $(date -u +%Y-%m-%dT%H:%M:%SZ)" > /dev/console; exec python3 -m http.server 8080 --bind 0.0.0.0 > /dev/console 2>&1' > /dev/null 2>&1 < /dev/null &
コンソール出力の取得
aws ec2 get-console-output --instance-id <instance-a-id> --latest --output text --query Output | grep -a A-CURL
aws ec2 get-console-output --instance-id <instance-b-id> --latest --output text --query Output | grep -aE 'B-SERVER|GET /|Serving HTTP'
コンソール出力は実時刻より遅れて反映され、検証中は取得時点と最新行の時刻に最大で約 50 秒の差がありました。時刻の比較には A の curl が書いた値を使っています。
VPC 側の設定
Transit Gateway の NAT 設定とは別に、VPC 側で相手の NAT アドレス宛の経路を作ります(B 側の経路は返信用です)。B のセキュリティグループには、A の変換後の送信元アドレスからの通信を許可します。
# VPC-A のルートテーブル: B の NAT アドレス宛を Transit Gateway へ
aws ec2 create-route --route-table-id <vpc-a-route-table-id> \
--destination-cidr-block 100.65.0.0/16 --transit-gateway-id <tgw-id>
# VPC-B のルートテーブル: A の NAT アドレス宛を Transit Gateway へ
aws ec2 create-route --route-table-id <vpc-b-route-table-id> \
--destination-cidr-block 100.64.0.0/16 --transit-gateway-id <tgw-id>
# VPC-B のセキュリティグループ: 変換後の A の送信元から TCP 8080 を許可
aws ec2 authorize-security-group-ingress --group-id <vpc-b-sg-id> \
--ip-permissions 'IpProtocol=tcp,FromPort=8080,ToPort=8080,IpRanges=[{CidrIp=100.64.0.0/16}]'
NAT 設定前のルートテーブルと疎通
NAT ルールを設定する前の Transit Gateway ルートテーブルを確認します。
aws ec2 search-transit-gateway-routes \
--transit-gateway-route-table-id <tgw-route-table-id> \
--filters Name=state,Values=active,blackhole \
--query 'Routes[].{Cidr:DestinationCidrBlock,Type:Type,State:State,Att:TransitGatewayAttachments[0].TransitGatewayAttachmentId}' \
--output json
[
{
"Cidr": "10.0.0.0/16",
"Type": "propagated",
"State": "active",
"Att": "tgw-attach-0123456789abcdef0"
}
]
10.0.0.0/16 のルートは A のアタッチメントから伝播した 1 件だけで、B からは伝播していません。VPC アタッチメントのドキュメントには、CIDR が重複する VPC をアタッチすると、後から接続した側のルートは伝播されないと書かれています。
VPC 側のルートを追加してからルールを設定するまでに、A の curl は 2 回実行され、どちらもタイムアウトしました(http=000、exit=28)。このとき Transit Gateway のルートテーブルに 100.65.0.0/16 のルートはありませんでした。
NAT ルールの設定
両方のアタッチメントに、RuleNumber 100 のルールを 1 つずつ設定します。MatchSourceCidr は VPC の CIDR と同じ 10.0.0.0/16 にします。ドキュメントによると、MatchSourceCidr が VPC の CIDR と完全に一致すると、Transit Gateway が NAT の変換先へのルートを自動で追加します。
# VPC-A のアタッチメント
aws ec2 modify-transit-gateway-attachment-nat-configuration \
--transit-gateway-attachment-id <attachment-a-id> \
--nat-gateway-support enable \
--nat-gateway-rules RuleNumber=100,MatchSourceCidr=10.0.0.0/16,NatCidr=100.64.0.0/16
# VPC-B のアタッチメント
aws ec2 modify-transit-gateway-attachment-nat-configuration \
--transit-gateway-attachment-id <attachment-b-id> \
--nat-gateway-support enable \
--nat-gateway-rules RuleNumber=100,MatchSourceCidr=10.0.0.0/16,NatCidr=100.65.0.0/16
ドキュメントによると、--nat-gateway-rules に渡したルールは、そのアタッチメントの既存ルールをすべて置き換えます。ルールが既にあるアタッチメントでは、残したいルールも含めて渡します。
反映状況は get-transit-gateway-attachment-nat-configuration で確認します。NatGatewaySupport が enabled、ルールの State が activated になるまで待ちます。次は、設定後に A のアタッチメントを確認したときの出力です。
aws ec2 get-transit-gateway-attachment-nat-configuration \
--transit-gateway-attachment-id <attachment-a-id>
{
"TransitGatewayAttachmentId": "tgw-attach-0123456789abcdef0",
"NatGatewaySupport": "enabled",
"NatSystemRouteSupport": "enable",
"NatGatewayRules": [
{
"RuleNumber": 100,
"MatchSourceCidr": "10.0.0.0/16",
"NatCidr": "100.64.0.0/16",
"State": "activated"
}
]
}
設定後のルートテーブルと送信元アドレス
設定前と同じコマンドでルートテーブルを取得しました。
[
{
"Cidr": "100.64.0.0/16",
"Type": "system",
"State": "active",
"Att": "tgw-attach-0123456789abcdef0"
},
{
"Cidr": "100.65.0.0/16",
"Type": "system",
"State": "active",
"Att": "tgw-attach-0fedcba987654321f"
}
]
設定前にあった 10.0.0.0/16 の伝播ルートは消え、各アタッチメントの変換先 CIDR を宛先とする system ルートが入っています。VPC 側のルートテーブルに追加した宛先も、この変換先 CIDR です。
次の図は、A から B への 1 回のリクエストでアドレスがどう変わるかを示しています。緑の部分は実測した値、黄色の部分は公式ドキュメントの説明に基づく値です。アタッチメントを通る途中のパケットは取得していません。
- 実測(緑): A の curl の宛先 100.65.1.10、Transit Gateway のルートテーブルにある 100.65.0.0/16 の system ルート、B のアクセスログの送信元 100.64.1.10、A の http=200
- ドキュメント(黄): A のアタッチメントのルールで送信元が 10.0.1.10 から 100.64.1.10 に変換され、B のアタッチメントのルールで宛先が 100.65.1.10 から B の実アドレス 10.0.1.10 に変換される

B のアクセスログでは、送信元が A の実 IP の 10.0.1.10 ではなく、変換後の 100.64.1.10 でした。
100.64.1.10 - - [25/Oct/2026 03:53:52] "GET / HTTP/1.1" 200 -
100.64.1.10 - - [25/Oct/2026 03:53:57] "GET / HTTP/1.1" 200 -
100.64.1.10 - - [25/Oct/2026 03:54:02] "GET / HTTP/1.1" 200 -
取得時点までのアクセスログ 18 件は、すべて同じ送信元でした。受信側のセキュリティグループやログは、変換後のアドレスで扱うことになります。
反映にかかった時間
ルールを設定したのは 03:52:54(UTC)です。その後の状態遷移を約 6 秒おきのポーリングで確認しました。03:53:06 の行は 03:53:00 と同じ状態なので省き、03:53:13 の行は A が遷移する直前の確認として残しています。各セルは「NatGatewaySupport / ルールの State」の順に示しています。表の時刻は状態を確認した時点のもので、実際の遷移はその直前の約 6 秒以内です。
| 時刻 (UTC) | A | B |
|---|---|---|
| 03:53:00 | enabling / activated | enabling / activated |
| 03:53:13 | enabling / activated | enabling / activated |
| 03:53:19 | enabled / activated | enabling / activated |
| 03:53:25 | enabled / activated | enabled / activated |
A の curl は、両方が enabled になったことを確認した後も失敗が続き、03:53:52 に初めて成功しました。
A-CURL 2026-10-25T03:53:28Z target=100.65.1.10 http=000 exit=28
A-CURL 2026-10-25T03:53:36Z target=100.65.1.10 http=000 exit=28
A-CURL 2026-10-25T03:53:44Z target=100.65.1.10 http=000 exit=28
A-CURL 2026-10-25T03:53:52Z target=100.65.1.10 http=200 exit=0
A-CURL 2026-10-25T03:53:57Z target=100.65.1.10 http=200 exit=0
A-CURL 2026-10-25T03:54:02Z target=100.65.1.10 http=200 exit=0
最後に失敗した curl は 03:53:44 に始めたものなので、疎通できるようになったのは 03:53:44〜03:53:52 の間です。ルール設定から疎通まで 50〜58 秒、B が enabled になったことを確認してからは 19〜27 秒でした。この値は、経路がない状態から初めて有効化したときの 1 回の測定です。使用中の通信に対するルール変更は測っていません。
ドキュメントには、有効化やルール変更のとき、変換とルートの反映時刻のずれで、NAT アドレス宛の通信は短時間落ちる場合があると書かれています。設定の反映は、API の状態に加えて疎通でも確認します。
導入前に確認する条件
公式ドキュメント(1:1 NAT の概要、仕組みと前提条件、ルーティングの動作、設定方法)に書かれている主な制限は次の 4 つです。
- 重複した CIDR の 2 つの VPC には、両方のアタッチメントに変換ルールを設定します。片側だけの場合、ルールのない側では返信先が自分の VPC 内のアドレスになり、VPC の local ルートで処理されて Transit Gateway に届きません。
- ルールの MatchSourceCidr が VPC の CIDR と完全に一致する場合、その VPC の実アドレス宛の伝播ルートが、NAT の変換先宛のルートに置き換わります。その VPC に実アドレスで通信していた他のアタッチメントは通信できなくなるので、設定する前に、変換後のアドレスを使うよう接続先を更新しておきます。
- NAT の変換先は、RFC 1918 または 100.64.0.0/10 の範囲で、元の CIDR と同じプレフィックス長にします。Transit Gateway が重複を検査するのは同じアタッチメント内のルールだけなので、VPC や他のアタッチメントの CIDR との重複は自分で避けます。
- アプライアンスモードを有効にした VPC アタッチメントには、NAT を設定できません。
1:1 NAT の追加料金の有無と単価は、Amazon VPC の料金ページで確認してください。
まとめ
従来、Transit Gateway では、CIDR が重複した VPC をつなぐことができない制約がありましたが、1:1 NAT をサポートしたことで回避可能になりました。
7 月のポリシーベースルーティング(PBR)など、Transit Gateway は大きなアップデートが続いています。VPC を Transit Gateway でつなぐ場合は、最新のアップデートを確認いただいたうえで、CIDR の重複を回避したい場合や、NAT の利用が望ましい背景がある場合に、今回対応した 1:1 NAT をお試しください。







