エラーの内容
EC2インスタンスにSSH接続して、パッケージをインストールしようとしたり、外部APIにアクセスしようとしたり、8.8.8.8にpingを打ったりすると、以下のエラーが発生します:
$ curl https://api.example.com
curl: (7) Failed to connect to api.example.com port 443: No route to host
$ ping 8.8.8.8
ping: connect: No route to host
$ yum update
Could not retrieve mirrorlist... connect: No route to host
深夜2時。デプロイは止まっている。インスタンスは起動しているし、セキュリティグループも問題なさそうなのに、アウトバウンド接続がことごとく失敗する。インターネットへの通信が一切届かない状態です。
根本原因
VPC内のEC2インスタンスは、デフォルトではインターネットアクセスが一切ありません。外部へのトラフィックには、明示的な経路が必要です。95%のケースは以下の2つのシナリオに当てはまります:
- パブリックサブネットにInternet Gateway(IGW)が存在しない: VPCにIGWがアタッチされていないか、ルートテーブルに
0.0.0.0/0 → igw-xxxxxxxxのエントリが存在しない。 - プライベートサブネットにNAT Gatewayが存在しない: インスタンスにパブリックIPがなく、アウトバウンドトラフィックを中継するNAT Gatewayも存在しない。
3番目のパターンとして、IGWやNAT Gatewayは存在するものの、サブネットのルートテーブルがそれらを指していないケースもあります。いずれにせよ、VPCがインターネットから遮断された状態です。
まず診断する
まだ何も変更しないでください。実際にどの障害が発生しているかを特定しましょう。
1. インスタンスにパブリックIPが割り当てられているか確認する
# インスタンス内部から実行
curl -s http://169.254.169.254/latest/meta-data/public-ipv4
# 何も返ってこない、または"404" = パブリックIPが割り当てられていない
2. AWS CLIでルートテーブルを確認する
# インスタンスが属するサブネットを確認
aws ec2 describe-instances \
--instance-ids i-0abc123def456 \
--query 'Reservations[].Instances[].SubnetId' \
--output text
# そのサブネットのルートテーブルを確認
aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=subnet-0xxxxxxx \
--query 'RouteTables[].Routes'
# 以下のようなルートを探す:
# { "DestinationCidrBlock": "0.0.0.0/0", "GatewayId": "igw-..." }
# 存在しない場合、それが原因です。
3. VPCにIGWがアタッチされているか確認する
aws ec2 describe-internet-gateways \
--filters Name=attachment.vpc-id,Values=vpc-0xxxxxxx \
--query 'InternetGateways[].InternetGatewayId'
# 空の配列 [] = IGWがアタッチされていない
修正A — パブリックサブネット:Internet Gatewayをアタッチする
インスタンスがパブリックサブネットにあり、パブリックIPを持つべき場合はこの方法で修正します。
手順1:IGWを作成してアタッチする
# ゲートウェイを作成
IGW_ID=$(aws ec2 create-internet-gateway \
--query 'InternetGateway.InternetGatewayId' \
--output text)
echo "Created IGW: $IGW_ID"
# VPCにアタッチ
aws ec2 attach-internet-gateway \
--internet-gateway-id $IGW_ID \
--vpc-id vpc-0xxxxxxx
手順2:ルートテーブルにデフォルトルートを追加する
# パブリックサブネットのルートテーブルIDを取得
RTB_ID=$(aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=subnet-0xxxxxxx \
--query 'RouteTables[].RouteTableId' \
--output text)
# IGWを指す0.0.0.0/0ルートを追加
aws ec2 create-route \
--route-table-id $RTB_ID \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID
手順3:インスタンスにパブリックIPが割り当てられているか確認する
パブリックIPなしで起動した場合は、Elastic IPを割り当てる必要があります:
# Elastic IPを確保
EIP_ALLOC=$(aws ec2 allocate-address --domain vpc \
--query 'AllocationId' --output text)
# インスタンスに関連付け
aws ec2 associate-address \
--instance-id i-0abc123def456 \
--allocation-id $EIP_ALLOC
修正B — プライベートサブネット:NAT Gatewayを追加する
プライベートサブネットのインスタンス(アプリサーバー、バックグラウンドワーカー、VPC内のLambdaなど)はパブリックIPを持ちません。アウトバウンドトラフィックを中継するために、パブリックサブネットにNAT Gatewayを配置する必要があります。
手順1:パブリックサブネットにNAT Gatewayを作成する
# NAT Gateway用のElastic IPを確保
EIP_ALLOC=$(aws ec2 allocate-address --domain vpc \
--query 'AllocationId' --output text)
# パブリックサブネット(プライベートではない)にNAT Gatewayを作成
NAT_ID=$(aws ec2 create-nat-gateway \
--subnet-id subnet-PUBLIC-0xxxxxxx \
--allocation-id $EIP_ALLOC \
--query 'NatGateway.NatGatewayId' \
--output text)
echo "NAT Gateway: $NAT_ID"
# 利用可能になるまで約60秒かかります
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_ID
手順2:プライベートサブネットのルートテーブルを更新する
# プライベートサブネットのルートテーブルを取得
PRIVATE_RTB=$(aws ec2 describe-route-tables \
--filters Name=association.subnet-id,Values=subnet-PRIVATE-0xxxxxxx \
--query 'RouteTables[].RouteTableId' \
--output text)
# すべてのアウトバウンドトラフィックをNAT Gateway経由でルーティング
aws ec2 create-route \
--route-table-id $PRIVATE_RTB \
--destination-cidr-block 0.0.0.0/0 \
--nat-gateway-id $NAT_ID
修正C — ルートは存在するが誤ったターゲットを指している場合
ルートテーブルにすでに 0.0.0.0/0 のエントリがあるが、削除済みまたはデタッチ済みのゲートウェイを指している場合。重複を追加するのではなく、既存のルートを置き換えます:
aws ec2 replace-route \
--route-table-id $RTB_ID \
--destination-cidr-block 0.0.0.0/0 \
--gateway-id $IGW_ID # プライベートサブネットの場合は --nat-gateway-id $NAT_ID
修正の確認
# インスタンスにSSH接続してアウトバウンド接続をテスト
curl -I https://www.google.com
# 期待する結果: HTTP/2 200
ping -c 3 8.8.8.8
# 期待する結果: 3 packets transmitted, 3 received
# ルートテーブルが正しいか確認
aws ec2 describe-route-tables \
--route-table-ids $RTB_ID \
--query 'RouteTables[].Routes[?DestinationCidrBlock==`0.0.0.0/0`]'
セキュリティグループの落とし穴
ゲートウェイの設定もルートも正しいのに、まだ No route to host が出る場合は、セキュリティグループのアウトバウンドルールを確認してください。デフォルトではすべてのエグレスが許可されていますが、誰かがある時点で制限をかけて、そのことを忘れているケースがよくあります:
aws ec2 describe-security-groups \
--group-ids sg-0xxxxxxx \
--query 'SecurityGroups[].IpPermissionsEgress'
# 以下のようなルールが含まれているべきです:
# { "IpProtocol": "-1", "IpRanges": [{ "CidrIp": "0.0.0.0/0" }] }
予防策
このエラーが発生する原因の9割は、手動でVPCを構築した場合や、Terraform/CloudFormationで自動生成した場合に、ゲートウェイやルートテーブルの設定を忘れることです。次回のために役立つ習慣をいくつか紹介します:
- AWSコンソールのVPC Reachability Analyzerを使うと、ネットワーク経路全体をトレースして、どこでトラフィックが遮断されているかを正確に教えてくれます。実際のパケットは送信されないので、推測は不要です。
- Terraformでサブネットに
tier=public/tier=privateタグを付ける。数週間後にVPCの設定を見直すとき、どのサブネットがIGW経由かNAT経由かが一目でわかります。 - Terraformでは、
aws_subnetと明示的なaws_route_table_associationを必ずセットで定義する。メインルートテーブルに依存するのは静かな落とし穴で、新しいサブネットがすべてそれを継承するため、メインテーブルを変更すると触っていない箇所まで壊れます。 - **CIDRの設計は重要です。**重複しているCIDRや不適切なサイズのサブネットは、デバッグが困難な微妙なルーティング問題を引き起こします。ToolCraftのSubnet Calculatorのようなブラウザベースのツールを使えば、VPCのCIDRを事前に適切に設計できます。情報が外部に送信されることもありません。
実際に動作するTerraformの最小構成
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}

