EC2 SSH接続タイムアウトの原因と修正方法 — セキュリティグループのインバウンドルール完全ガイド

新しいEC2インスタンスを起動してSSH接続を試みたとき、ssh: connect to host xx.xx.xx.xx port 22: Connection timed out というエラーで止まった経験は多くのエンジニアにある。アプリケーションのエラーではなく、パケットがインスタンスに届いていないことを示すタイムアウトだ。原因の大半はセキュリティグループのインバウンドルール設定にあるが、それだけで解決しないケースも存在する。

TL;DR — EC2 SSHタイムアウトの主な原因と対処

原因レイヤー確認項目修正方向
セキュリティグループポート22のインバウンドルールが存在するかTCP 22をソースIPに対して許可
ネットワークACLサブネットレベルでポート22がブロックされていないかインバウンド/アウトバウンド両方のルールを確認
パブリックIPアドレスインスタンスにパブリックIPが割り当てられているかElasticIPの関連付けまたはパブリックIP自動割り当ての有効化
インターネットゲートウェイVPCにIGWがアタッチされているかIGWを作成してVPCにアタッチ
ルートテーブルサブネットのルートテーブルに0.0.0.0/0 → IGWのルートがあるかデフォルトルートをIGWに向ける

EC2 SSH接続の仕組み — パケットが届くまでの経路

SSHタイムアウトを正確に診断するには、クライアントのパケットがEC2インスタンスに到達するまでに通過するレイヤーを理解する必要がある。セキュリティグループだけを見て終わりにすると、別のレイヤーでブロックされているケースを見逃す。

graph LR Client["クライアント
ローカルマシン"] -->|"TCP SYN port 22"| IGW["インターネット
ゲートウェイ (IGW)"] IGW --> RT["ルートテーブル
0.0.0.0/0 → IGW"] RT --> NACL["ネットワークACL
サブネットレベル (ステートレス)"] NACL --> SG["セキュリティグループ
インスタンスレベル (ステートフル)"] SG --> EC2["EC2インスタンス
sshd port 22"] style Client fill:#f0f4ff,stroke:#4a6cf7 style IGW fill:#fff3e0,stroke:#ff9800 style RT fill:#fff3e0,stroke:#ff9800 style NACL fill:#fce4ec,stroke:#e91e63 style SG fill:#fce4ec,stroke:#e91e63 style EC2 fill:#e8f5e9,stroke:#4caf50
  1. クライアント: ローカルマシンからポート22へTCP SYNパケットを送信する。
  2. インターネットゲートウェイ (IGW): VPCへの入口。IGWがなければパケットはVPCに入れない。
  3. ルートテーブル: サブネットに関連付けられたルートテーブルが、パケットをどこに転送するかを決定する。パブリックサブネットには 0.0.0.0/0 → IGW のルートが必要だ。
  4. ネットワークACL (NACL): サブネットレベルのステートレスフィルター。インバウンドとアウトバウンドの両方でルールが評価される。
  5. セキュリティグループ: インスタンスレベルのステートフルフィルター。ここでTCP 22が許可されていなければパケットはインスタンスに届かない。
  6. EC2インスタンス: OS内のsshdがポート22でリッスンしていることが前提。
セキュリティグループはステートフルなので、インバウンドを許可すれば戻りのパケットは自動的に許可される。一方、NACLはステートレスなので、インバウンドとアウトバウンドを個別に設定しなければならない。この違いを混同すると診断が迷走する。

EC2 SSHタイムアウトの診断手順

Step 1: セキュリティグループのインバウンドルールを確認する

タイムアウトの最も一般的な原因はここだ。インスタンスにアタッチされているセキュリティグループに、ポート22のインバウンドルールが存在するかを確認する。まずインスタンスIDからセキュリティグループIDを特定し、そのルールを検査する。

# インスタンスにアタッチされているセキュリティグループIDを取得
aws ec2 describe-instances \
  --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[*].Instances[*].SecurityGroups' \
  --output table

# セキュリティグループのインバウンドルールを確認
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0 \
  --query 'SecurityGroups[*].IpPermissions' \
  --output json

出力にポート22 (FromPort: 22, ToPort: 22, IpProtocol: tcp) のエントリがなければ、ルールを追加する必要がある。ソースIPは自分のIPアドレスに絞るのが最小権限の原則に沿っている。0.0.0.0/0 を使うと全インターネットからのアクセスを許可することになるため、本番環境では避けること。

# 自分のIPアドレスのみSSHを許可するルールを追加
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 22 \
  --cidr 203.0.113.10/32

Step 2: インスタンスにパブリックIPアドレスが割り当てられているか確認する

セキュリティグループにルールを追加しても接続できない場合、インスタンスにパブリックIPアドレスがそもそも存在しないことがある。プライベートIPアドレスにはインターネットから直接到達できない。

aws ec2 describe-instances \
  --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[*].Instances[*].{PublicIP:PublicIpAddress,PrivateIP:PrivateIpAddress,State:State.Name}' \
  --output table

PublicIPNone または空の場合、Elastic IPを割り当てて関連付けるか、インスタンスを停止してサブネットのパブリックIP自動割り当て設定を有効にした上で再起動する必要がある。実行中のインスタンスにパブリックIPを後付けするにはElastic IPが唯一の手段だ。

# Elastic IPを割り当て
aws ec2 allocate-address --domain vpc

# 割り当てたElastic IPをインスタンスに関連付け (AllocationIdは上のコマンドの出力から取得)
aws ec2 associate-address \
  --instance-id i-0123456789abcdef0 \
  --allocation-id eipalloc-0123456789abcdef0

Step 3: VPCにインターネットゲートウェイがアタッチされているか確認する

パブリックIPがあってもIGWがなければパケットはVPCに入れない。VPCのIGWアタッチ状態を確認する。

aws ec2 describe-internet-gateways \
  --filters Name=attachment.vpc-id,Values=vpc-0123456789abcdef0 \
  --query 'InternetGateways[*].{IGW_ID:InternetGatewayId,State:Attachments[0].State}' \
  --output table

State: available のIGWが存在しない場合は作成してアタッチする。

# IGWを作成
aws ec2 create-internet-gateway

# VPCにアタッチ (InternetGatewayIdは上のコマンドの出力から取得)
aws ec2 attach-internet-gateway \
  --internet-gateway-id igw-0123456789abcdef0 \
  --vpc-id vpc-0123456789abcdef0

Step 4: サブネットのルートテーブルにデフォルトルートがあるか確認する

IGWが存在していても、インスタンスのサブネットに関連付けられたルートテーブルに 0.0.0.0/0 → IGW のルートがなければ、アウトバウンドのパケットがIGWに転送されない。インターネットからの戻りパケットも同様に経路がなくなる。

# インスタンスのサブネットIDを取得
aws ec2 describe-instances \
  --instance-ids i-0123456789abcdef0 \
  --query 'Reservations[*].Instances[*].SubnetId' \
  --output text

# サブネットに関連付けられたルートテーブルを確認
aws ec2 describe-route-tables \
  --filters Name=association.subnet-id,Values=subnet-0123456789abcdef0 \
  --query 'RouteTables[*].Routes' \
  --output json

出力に DestinationCidrBlock: 0.0.0.0/0 かつ GatewayId: igw-... のエントリがなければ、ルートを追加する。

# ルートテーブルIDを取得した上でデフォルトルートを追加
aws ec2 create-route \
  --route-table-id rtb-0123456789abcdef0 \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-0123456789abcdef0

Step 5: ネットワークACLがポート22をブロックしていないか確認する

ここまで全て問題なくても接続できない場合、サブネットレベルのNACLが原因である可能性がある。NACLはステートレスなので、インバウンドのポート22許可だけでなく、アウトバウンドのエフェメラルポート (1024-65535) の許可も必要だ。これを見落とすと、SYNは届くがSYN-ACKが返せずタイムアウトになる。

# サブネットに関連付けられたNACLを確認
aws ec2 describe-network-acls \
  --filters Name=association.subnet-id,Values=subnet-0123456789abcdef0 \
  --query 'NetworkAcls[*].{NACL_ID:NetworkAclId,Entries:Entries}' \
  --output json

デフォルトNACLは全トラフィックを許可するが、カスタムNACLを使っている場合は明示的なDENYルールが存在することがある。ルール番号が小さいほど優先度が高いため、DENY ルールがALLOWルールより前に評価されていないかを確認する。

graph TD Start["SSH接続試行"] --> CheckSG{"SGにポート22
インバウンドルールあり?"} CheckSG -->|"なし"| FixSG["SGにTCP 22ルールを追加"] CheckSG -->|"あり"| CheckIP{"インスタンスに
パブリックIPあり?"} CheckIP -->|"なし"| FixIP["Elastic IPを割り当て・関連付け"] CheckIP -->|"あり"| CheckIGW{"VPCにIGWが
アタッチされている?"} CheckIGW -->|"なし"| FixIGW["IGWを作成してVPCにアタッチ"] CheckIGW -->|"あり"| CheckRT{"ルートテーブルに
0.0.0.0/0→IGWあり?"} CheckRT -->|"なし"| FixRT["デフォルトルートをIGWに追加"] CheckRT -->|"あり"| CheckNACL{"NACLがポート22または
エフェメラルポートをブロック?"} CheckNACL -->|"はい"| FixNACL["NACLのインバウンド/アウトバウンドルールを修正"] CheckNACL -->|"いいえ"| CheckSSHD["インスタンス内のsshd動作を確認"] FixSG --> Done["接続確認"] FixIP --> Done FixIGW --> Done FixRT --> Done FixNACL --> Done style Start fill:#e3f2fd,stroke:#1976d2 style Done fill:#e8f5e9,stroke:#388e3c style FixSG fill:#fff9c4,stroke:#f9a825 style FixIP fill:#fff9c4,stroke:#f9a825 style FixIGW fill:#fff9c4,stroke:#f9a825 style FixRT fill:#fff9c4,stroke:#f9a825 style FixNACL fill:#fff9c4,stroke:#f9a825

よくある誤診断 — セキュリティグループを直してもまだ繋がらないケース

実際の現場でよく見るパターンがある。セキュリティグループにポート22のルールを追加したのに接続できず、「セキュリティグループの反映に時間がかかるのか」と数分待ち続けるケースだ。セキュリティグループの変更は即時反映される。待っても状況は変わらない。

この場合の実際の原因は大抵、インスタンスがプライベートサブネットに配置されていてパブリックIPを持っていないか、ルートテーブルにIGWへのルートがないかのどちらかだ。aws ec2 describe-instances でパブリックIPの有無を確認するのが最初の確認ステップとして正しい。

もう一つのパターン: カスタムNACLを持つ環境でセキュリティグループとルートテーブルを修正したが繋がらない。NACLのアウトバウンドルールにエフェメラルポート範囲の許可がなく、TCP ハンドシェイクの応答パケットがドロップされていた。セキュリティグループはステートフルなので戻りパケットを気にしなくていいが、NACLはステートレスなのでこの違いが盲点になりやすい。

最小権限のセキュリティグループ設定例

SSH接続用のセキュリティグループを新規作成する場合の参考設定を示す。ソースIPは必ず自分の組織のIPアドレスに絞ること。

🔽 セキュリティグループ作成とSSHルール追加のCLI例 (クリックして展開)
# セキュリティグループを作成
aws ec2 create-security-group \
  --group-name ssh-access-sg \
  --description 'SSH access from corporate IP only' \
  --vpc-id vpc-0123456789abcdef0

# 出力されたGroupIdを使ってSSHインバウンドルールを追加
# 203.0.113.0/24 は自組織のIPレンジに置き換えること
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 22 \
  --cidr 203.0.113.0/24

# 設定を確認
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0 \
  --query 'SecurityGroups[*].{Name:GroupName,Rules:IpPermissions}' \
  --output json

IAMポリシー — EC2とVPCリソースの読み取りに必要な権限

診断用のCLIコマンドを実行するIAMユーザーまたはロールには、以下の権限が必要だ。変更操作を行う場合は対応するWrite権限も必要になる。

🔽 診断用IAMポリシー例 (クリックして展開)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EC2ReadForDiagnostics",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeNetworkAcls",
        "ec2:DescribeRouteTables",
        "ec2:DescribeInternetGateways",
        "ec2:DescribeSubnets",
        "ec2:DescribeAddresses"
      ],
      "Resource": "*"
    },
    {
      "Sid": "EC2WriteForRemediation",
      "Effect": "Allow",
      "Action": [
        "ec2:AuthorizeSecurityGroupIngress",
        "ec2:CreateRoute",
        "ec2:AttachInternetGateway",
        "ec2:AssociateAddress",
        "ec2:AllocateAddress"
      ],
      "Resource": "*"
    }
  ]
}

Read/List/Describe系のアクションはリソースレベルの制限をサポートしないものが多く、"Resource": "*" が必要になる。AWS Service Authorization Referenceで各アクションのリソースレベルサポートを確認すること。

EC2 SSH接続タイムアウト — まとめと次のステップ

SSH接続タイムアウトの診断は、セキュリティグループだけを見て終わりにしないことが重要だ。パケット経路全体 (IGW → ルートテーブル → NACL → セキュリティグループ) を順番に確認することで、どのレイヤーでブロックされているかを特定できる。

本番環境でSSHを常時開放するのは避け、AWS Systems Manager Session Managerを使ったポートレスアクセスへの移行も検討する価値がある。Session Managerを使えばポート22を一切開放せずにインスタンスにアクセスできる。

用語集

用語説明
セキュリティグループ (SG)EC2インスタンスレベルのステートフルな仮想ファイアウォール。インバウンドを許可すれば戻りのトラフィックは自動的に許可される。
ネットワークACL (NACL)サブネットレベルのステートレスなフィルター。インバウンドとアウトバウンドのルールを個別に設定する必要がある。
インターネットゲートウェイ (IGW)VPCとインターネット間の通信を可能にするVPCコンポーネント。パブリックサブネットからのインターネットアクセスに必須。
エフェメラルポートTCPセッションの応答に使われる一時的なポート。クライアントOSによって範囲が異なるが、一般的に1024-65535の範囲。NACLのアウトバウンドルールで許可が必要。
Elastic IP (EIP)AWSアカウントに割り当て可能な静的なパブリックIPアドレス。実行中のインスタンスにパブリックIPを後付けする唯一の手段。

コメント

このブログの人気の投稿

S3パブリックアクセス拒否の原因と解決策:バケットレベルの「Block Public Access」が優先される仕組み

EC2インスタンスIDをメタデータから取得する方法 — IMDSv2が安全な理由