Terraform の remote-exec における SSH タイムアウトと接続エラーの修正方法

intermediate🏗️ Terraform2026-06-14| Terraform 1.0+, AWS/Azure/GCP, Linux インスタンス (Ubuntu, RHEL, Amazon Linux)

Error Message

Error: timeout - last error: dial tcp 10.0.1.5:22: connect: connection refused
#terraform#aws#ssh#devops#トラブルシューティング

プロビジョナーの罠ついに Terraform のマニフェストが完成しました。terraform apply を実行し、インスタンスが立ち上がるのを眺めます。そして...沈黙。ターミナルは5分間停止したままになり、最終的におなじみのエラーでクラッシュします。

Error: timeout - last error: dial tcp 10.0.1.5:22: connect: connection refused

これは通常、Terraform を実行している環境(ローカル PC、GitHub Actions エージェント、CI/CD パイプラインなど)が SSH ポートに到達できないことを意味します。イライラする障害ですが、多くの場合、解決は簡単です。冷静にデバッグを進めるための手順を紹介します。

TL;DR: 60秒のクイックチェック- セキュリティグループ: 特定の IP に対してポート 22 が開放されていますか?インバウンドルールを確認してください。- IP の選択: パブリックインターネットからプライベート IP に接続しようとしていませんか? hostpublic_ip に変更してください。- 起動の遅延: AWS コンソールで「実行中」となっていても、sshd の初期化が完了していない場合があります。- ユーザー名: Amazon Linux AMI に対して ubuntu を使用していませんか?(正しいユーザー名は ec2-user です)。### 1. ルーティングの不一致: パブリック IP vs. プライベート IPエラーに含まれる IP(例: 10.0.1.5)をよく確認してください。ローカルマシンから Terraform を実行している場合、そのアドレスにはトラフィックをルーティングできません。それはプライベートな内部 IP です。Terraform は最初に見つけた IP(通常は内部 IP)を自動的に取得することがよくあります。

remote-exec プロビジョナーには直接の通信経路が必要です。VPN を使用しているか、踏み台ホスト(Bastion host)を経由していない限り、インスタンスのパブリック IP を使用する必要があります。

解決策:connection ブロックでパブリック IP 属性を使用するように明示します。

resource "aws_instance" "web" {
  # ... 設定 ...

  provisioner "remote-exec" {
    connection {
      type        = "ssh"
      user        = "ubuntu"
      private_key = file("~/.ssh/deploy_key")
      host        = self.public_ip # 自動選択に任せず明示的に指定
    }

    inline = ["sudo apt-get update"]
  }
}

2. セキュリティグループ: 見えない壁正しい IP を使用していても、timeout が発生する場合はファイアウォールがパケットを暗黙的にドロップしていることを示唆しています。一方、connection refused はサーバーに到達したものの接続を拒否されたことを意味します。ほとんどのクラウドプロバイダーは、インバウンドトラフィックをデフォルトで「すべて拒否」に設定しています。

次のステップ:セキュリティグループまたはネットワークセキュリティグループが TCP ポート 22 を許可しているか確認します。テスト目的では 0.0.0.0/0 を使用することもありますが、本番環境では特定の IP 範囲(例: 203.0.113.5/32)に制限してください。

resource "aws_security_group" "ssh_access" {
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["ここに自分のIPを入力/32"]
  }
}

3. レースコンディション: 起動時間の現実クラウドの API は高速ですが、Linux カーネルの起動はそれよりも遅いです。AWS の t3.micro は 20 秒以内に「実行中」と報告されることがありますが、cloud-initsshd が完全に起動するにはさらに 40〜60 秒かかることがよくあります。Terraform が早すぎるタイミングで接続を試みると、リトライ回数を使い果たしてしまう可能性があります。

解決方法:OS が起動する余裕を持たせるために、接続タイムアウトを長く設定します。標準的なイメージの場合、通常 5〜10 分に設定すれば安全です。

connection {
  type    = "ssh"
  user    = "ec2-user"
  host    = self.public_ip
  timeout = "10m" 
}

4. ローカルファイアウォール (UFW/Firewalld)セキュリティが強化された一部の AMI では、内部ファイアウォールが有効になっています。クラウドレベルのセキュリティグループが全開放されていても、Ubuntu の ufw や RHEL の firewalld がポート 22 をブロックしている可能性があります。

手動で確認して、Terraform 固有の問題でないことを切り分けます。

ssh -i ~/.ssh/key.pem user@1.2.3.4

この手動コマンドが失敗する場合、問題は Terraform のコードではなく、ネットワークまたは OS の設定にあります。

上級者のテクニック: プロビジョナーを使わないHashiCorp はプロビジョナーを「最終手段」と考えています。プロビジョナーは Terraform のステート(状態)に含まれないため、動作が不安定になりやすいからです。ネットワークの不安定さが頻繁に問題になる場合は、セットアップのロジックを user_data に移行してください。

Cloud-init はマシン上でローカルに実行されます。ローカル PC からの SSH トンネルを必要としないため、インスタンスのブートストラップにおいて 10 倍信頼性が高まります。

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t3.micro"
  user_data     = <<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              EOF
}

最終チェックリスト- サブネット: インスタンスはプライベートサブネットにありますか?その場合、踏み台(Bastion)または VPN が必要です。- キーの権限: 秘密鍵が保護されていることを確認してください(chmod 400)。- ユーザー名: AMI のデフォルトユーザー名を再確認してください。OS が ubuntu を期待しているときに rootadmin を使用すると、必ず失敗します。

Related Error Notes