ECONNREFUSED(Connection refused)
「Connection refused」の正体はポートで待ち受けるプロセス不在の即時拒否。原因の切り分け表と ss/nc の診断手順で、数分で原因を特定し復旧できる。
- ECONNREFUSED は接続先のIP:ポートまで到達したが、そのポートで LISTEN しているプロセスが無く、相手OSが TCP RST(RST/ACK)を即返した状態。タイムアウトではなく即時失敗が特徴。
- 頻度順の主因はサービス未起動・ポート番号違い・127.0.0.1 バインドでリモート接続不可・別ホストへ接続。ファイアウォールは通常ドロップ(無応答→タイムアウト)なので RST が返る時点で経路上のフィルタは考えにくい。
- 診断は ss -ltnp で待ち受け確認、nc -vz で到達性、バインドアドレスが 0.0.0.0 か 127.0.0.1 かを確認。解決はサービス起動・ポート統一・待ち受けアドレス修正の順で切り分ける。
一言でいうと(このエラーが何を意味するか)
ECONNREFUSED(メッセージ文言は connect: Connection refused)は、接続先のIPアドレスとポートまでパケットは届いたのに、そのポートで待ち受けている(LISTEN 状態の)プロセスが存在しなかったことを示すエラーです。POSIX の connect(2) が返す errno の一つで、Node.js・Python・Go・curl・psql など言語やツールを問わず同じ根本原因を指します。
TCP では、クライアントが SYN を送ったとき、その宛先ポートに LISTEN 中のソケットが無いと、相手のOSカーネルが RST(リセット)フラグを立てたセグメント(正確には RST/ACK)を即座に返します。クライアントのカーネルはこの RST を受けて connect() を ECONNREFUSED で失敗させます。ここで決定的に重要なのは即座に返るという挙動です。パケットが相手ホストのカーネルまで到達している証拠であり、名前解決も経路(ルーティング)も正常だと分かります。問題はもっと手前、そのポートで誰も聞いていないという一点に絞り込めます。
ファイアウォールがパケットを黙って破棄(DROP)する場合、クライアントには何の応答も返らず、SYN の再送を繰り返した末に ETIMEDOUT(Connection timed out)になります。つまり「即座に Refused」なら相手ホストは生きていてポートが閉じている、「数秒〜数十秒沈黙してから Timed out」なら経路上でパケットが握り潰されている、と切り分けられます。この差は原因究明の最初の分岐点です。
横にスクロール
よくある原因(複数を具体的に、頻度順)
| 原因 | 典型的な状況 | 見分け方 |
|---|---|---|
| サービス未起動 | アプリ/DB がクラッシュ・未デプロイ・再起動中 | ss にポートが出ない。プロセスが落ちている |
| ポート番号違い | 設定と実際の待ち受けが不一致(3000 と 8080 等) | ss には別ポートで LISTEN が見える |
| 127.0.0.1 バインド | サーバーがループバックのみで待ち受け、外部から不可 | ss で Local Address が 127.0.0.1 になっている |
| 別ホストへ接続 | 接続先IP/ホスト名の誤り、コンテナ名前解決の失敗 | そのIPではサービスが動いていない |
| 起動直後の競合 | コンテナ/サービスがまだ bind 完了前にアクセス | 少し待つと成功する。ヘルスチェック不足 |
1. サービスが起動していない(最頻)。 接続先のプロセス自体が動いていないケースです。デプロイ漏れ、クラッシュ後に再起動されていない、systemctl start を忘れた、コンテナが起動失敗している、などです。RST を返すのは相手ホストのカーネルなので、ホスト自体は生きているがアプリだけ死んでいる状況で典型的に起こります。
2. ポート番号が食い違っている。 クライアントが :3000 に繋ぎに行くのに、サーバーは :8080 で待っている、といった不一致です。環境変数の設定ミスや、開発と本番でポートが違うのに設定が追随していない場合に頻発します。
3. 待ち受けアドレスが 127.0.0.1(ループバック)に限定されている。 サーバーは起動しているのに、127.0.0.1(loopback)だけにバインドしていると、同一ホスト内からは繋がるのに他マシンやコンテナからは ECONNREFUSED になります。カーネルは loopback 宛のパケットを外部NICで受けないため、外から来た SYN に対しては「そのポートは開いていない」扱いとなり RST を返します。データベースやアプリサーバーで既定が 127.0.0.1 になっている製品は多く、リモート公開には 0.0.0.0(全アドレス)や特定NICへのバインド変更が要ります。
4. そもそも別のホストに繋ぎに行っている。 接続先IP/ホスト名の誤り、/etc/hosts の残骸、Docker/Kubernetes でサービス名の解決先を取り違えている、などです。到達先ホストは応答するがそのポートは使っていない、という形で Refused になります。
5. 起動直後の競合(レースコンディション)。 コンテナ群を同時起動したとき、依存先がまだ bind()/listen() を終える前に接続しに行くと一瞬だけ Refused が出ます。ヘルスチェックやリトライ、depends_on の待機条件が不足しているサインです。
診断の手順(切り分け方・見るべきログ/コマンド)
原則は「相手ホストで聞いているか」→「経路は通るか」→「アプリのログ」の順に外側から詰めることです。
手順1: 接続先ホスト上で LISTEN を確認する。 まず相手側で、狙ったポートが本当に待ち受けているかを見ます。ss が現在の標準(netstat の後継)です。
# -l LISTEN のみ / -t TCP / -n 名前解決しない / -p プロセス表示
ss -ltnp | grep ':3000'
# 出力例: LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=812,fd=20))
ここで確認すべきは2点です。第一にそのポートの行が存在するか(無ければ原因1のサービス未起動、または原因2のポート違い)。第二に Local Address が 0.0.0.0:3000 か 127.0.0.1:3000 かです。上の例は 127.0.0.1 なので、同一ホストからは繋がってもリモートからは Refused になります(原因3)。0.0.0.0 や [::] なら全インターフェースで待ち受けています。lsof でも同じ確認ができます。
lsof -iTCP:3000 -sTCP:LISTEN -P -n
手順2: クライアント側から到達性を測る。 次に接続元から、L3/L4 が通るかを確かめます。nc(netcat)か telnet が簡便です。
# -v 詳細 / -z データを送らず接続可否だけ / -w タイムアウト秒
nc -vz -w3 db.internal 5432
# 成功: Connection to db.internal 5432 port [tcp/postgresql] succeeded!
# 失敗: nc: connect to db.internal port 5432 (tcp) failed: Connection refused
ここでの結果が切り分けの核心です。Connection refused が即座に返るなら、パケットは相手ホストまで届いており、ポートが閉じている(アプリ側の問題)と確定します。逆に数秒沈黙してから timed out になるなら、経路上のファイアウォールがパケットを DROP している可能性が高く、原因はネットワーク側に移ります。curl でHTTPを試す場合も同じ errno がメッセージに出ます。
curl -v http://api.internal:8080/health
# curl: (7) Failed to connect to api.internal port 8080: Connection refused
ECONNREFUSED(RST 応答)が返っている時点で、SYN は相手カーネルに到達しています。したがって「経路上のファイアウォールでブロックされている」という仮説はいったん外して良いのが定石です(DROP なら無応答=タイムアウトになるため)。ただし一部のファイアウォールは REJECT ポリシーで RST や ICMP port-unreachable を能動的に返す設定も可能で、その場合はホスト上に LISTEN が有るのに Refused になり得ます。手順1で LISTEN を確認済みなら、この REJECT を疑います。
手順3: アプリと接続文字列のログを読む。 LISTEN が存在するのに繋がらないなら、バインドアドレスの不一致か、接続先の指定ミスです。アプリ起動ログの Listening on ... 行のアドレス部を確認し(0.0.0.0 か localhost か)、クライアント側の接続文字列(ホスト名・ポート・DATABASE_URL 等の環境変数)が実際の待ち受けと一致しているかを突き合わせます。Docker では、コンテナ内の localhost はそのコンテナ自身を指すため、他コンテナへはサービス名で接続する必要があり、ここが典型的な取り違えポイントです。
解決と予防(対処と再発防止)
診断で切り分けた原因ごとに、対処は次のとおりです。
- サービス未起動(原因1): プロセスを起動し、
ss -ltnpで LISTEN を確認。クラッシュ再発はsystemdのRestart=on-failureやオーケストレータの再起動ポリシーで自動復帰させる。 - ポート違い(原因2): クライアントとサーバーの設定を単一の出所(環境変数・設定ファイル)に統一し、両者が同じ値を参照するようにする。ハードコードを排除する。
127.0.0.1バインド(原因3): リモート公開が必要なら待ち受けを0.0.0.0(または該当NICのIP)に変更する。ただし無防備な全公開は避け、ファイアウォール・セキュリティグループでアクセス元を絞るのが前提。- 接続先誤り(原因4): ホスト名・IP・DNS/
/etc/hostsを点検。コンテナ環境ではサービス名解決を使う。 - 起動レース(原因5): 接続側にリトライ(指数バックオフ)を実装し、依存先のヘルスチェック成功を待ってからトラフィックを流す。
depends_onのcondition: service_healthy等を使う。
ECONNREFUSED を消したい一心で待ち受けを 0.0.0.0 に開くと、意図せず全ネットワークへサービスを露出させることがあります。特にデータベースや管理用ポートは、バインド変更とファイアウォール/セキュリティグループによるアクセス制限を必ずセットで行ってください。既定が 127.0.0.1 なのは安全側に倒した設計です。
TCP のハンドシェイクと RST の意味、ポート・ソケットの動作はネットワークを、bind()/listen() とループバックの扱いはOSを、バインド公開に伴うアクセス制御の設計はセキュリティを参照してください。
- ECONNREFUSED はホスト到達済み+ポート未 LISTEN で、相手カーネルが RST を即返した状態。タイムアウト(DROP による無応答)とは区別する
- RST が返る時点で経路は通っている=原因はアプリ側(未起動・ポート違い・バインドアドレス)に絞れる
- 127.0.0.1 バインドは同一ホストのみ可。リモート公開は 0.0.0.0 だがアクセス制御と必ずセット
- 診断は ss -ltnp(待ち受け)→ nc -vz(到達性)→ アプリログ(バインド/接続文字列)の順で外側から詰める
エラー辞典の記事ガイド
ECONNREFUSED(Connection refused)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
エラー
比較で見る軸
難易度: advanced / カテゴリ: エラー辞典 / タグ数: 6
導入後に効く点
頻度順の主因はサービス未起動・ポート番号違い・127.0.0.1 バインドでリモート接続不可・別ホストへ接続。ファイアウォールは通常ドロップ(無応答→タイムアウト)なので RST が返る時点で経路上のフィルタは考えにくい。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- エラー辞典
- タグ数
- 6
判断チェックリスト
- 自社の用途が「エラー / TCP」に近いか確認する。
- 強みである「ECONNREFUSED は接続先のIP:ポートまで到達したが、そのポートで LISTEN しているプロセスが無く、相手OSが TCP RST(RST/ACK)を即返した状態。タイムアウトではなく即時失敗が特徴。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。