「Connection refused」の切り分け - サービス停止かポート閉塞か
「Connection refused」とは何を意味するのか?
結論: 相手ホストには届いているが、そのポートで接続を能動的に拒否された状態。「届かない」のではなく「届いた上で断られた」点が重要で、原因の大半は接続先でサービスが起きていないこと。
Connection refused は、TCP 接続要求(SYN)に対して相手から RST(リセット) が返ってきたときに出る。つまりパケットは相手ホストに到達している。ホスト自身、またはその手前の機器が「このポートは受け付けない」と明示的に応答した結果だ。
$ curl http://192.0.2.10:8080 curl: (7) Failed to connect to 192.0.2.10 port 8080 after 3 ms: Connection refused
$ ssh user@192.0.2.10 ssh: connect to host 192.0.2.10 port 22: Connection refused
システムコールレベルでは ECONNREFUSED というエラーで、アプリはこれを「Connection refused」と表示する。応答が即座に返ってくる(数ミリ秒)のも特徴で、これが後述の timeout との大きな違いになる。
前提(対象環境)
- OS: Ubuntu / 一般的な Linux
- 接続先: TCP で待ち受けるサービス(SSH / Web / DB 等)
- クライアント・サーバ双方にシェルで入れる前提で進める
Connection refused と timeout はどう違うのか?
結論: refused は RST が即返る(ホストは生きている)、timeout は応答が全く返らない(パケットが捨てられている)。この違いだけで、原因がサービス側かネットワーク経路側かをほぼ二分できる。
最初に見るべきは「拒否されたのか」「無応答なのか」だ。エラーメッセージと応答までの時間で見分ける。
| 症状 | 返ってくるもの | 主な原因 |
|---|---|---|
| Connection refused | TCP RST(即時) | サービス停止 / ポート違い / REJECT ルール |
| Connection timed out | 無応答(数十秒待つ) | DROP するファイアウォール / 経路不通 / 機器停止 |
$ time nc -zv 192.0.2.10 22
nc: connect to 192.0.2.10 port 22 (tcp) failed: Connection refused real 0m0.004s
応答が 0.0xx 秒で返れば refused(ホストは応答している)。十数秒〜数十秒待たされた末に失敗するなら timeout で、これは ファイアウォールの DROP や経路の問題を疑う。本記事は refused 側を扱う。timeout 寄りの切り分けは ポート疎通の確認も参照。
「ホストは生きているがポートで断られている」のが refused。ping が通るかどうかは判断材料にならない(ICMP と TCP は別物で、ping 不可でも SSH は通ることがある)。
なぜ Connection refused が出るのか?
結論: 9 割は「接続先ポートで誰も Listen していない」。サービス停止・ポート番号違い・Listen アドレス違い・REJECT ルールの 4 つに整理でき、上から順に潰すのが速い。
refused が返る経路を原因別に並べると次のとおり。
| 原因 | 起きやすい状況 | 確認の起点 |
|---|---|---|
| サービスが停止している | 落ちた / 起動失敗 / 再起動後に上がっていない | systemctl status |
| ポート番号が違う | 設定変更・デフォルト以外で待ち受けている | ss -tlnp |
| Listen アドレスが違う | 127.0.0.1 だけで待ち受け、外から繋がらない |
ss -tlnp |
| ファイアウォールが REJECT | nftables/iptables が RST/ICMP で能動拒否している | ss + ルール確認 |
切り分けは サーバ側で「本当に Listen しているか」を見る ところから始めるのが確実だ。クライアント側のエラーだけ見ても、サービス停止とポート違いは区別できない。
サービスが起きているか確認するには?
結論: サーバ上で
ss -tlnpを見て、目的のポートが LISTEN にあるか確認する。無ければサービス停止かポート違い。systemctl statusとjournalctlで起動状態と理由を裏取りする。
まずサーバにログインし、待ち受けているポート一覧を見る。
$ sudo ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=812,fd=3))
LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("node",pid=1043,fd=18))
ss のオプションは -t(TCP)-l(LISTEN のみ)-n(名前解決せず数値表示)-p(プロセス表示、要 root)。目的のポートがこの一覧に無ければ、そのポートでは誰も待ち受けていない。クライアントから見れば即 refused になる。
ポートが見当たらないときは、対応するサービスの状態を確認する。
$ systemctl status nginx
× nginx.service - A high performance web server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled)
Active: failed (Result: exit-code) since ...
Active: failed や inactive (dead) なら停止している。起動を試み、失敗するなら理由をログで追う。
$ sudo systemctl start nginx $ journalctl -u nginx -n 30 --no-pager
サービスが上がっているのに refused が出る場合は、ほぼ Listen アドレスかポート番号の食い違い。次のセクションへ進む。
Listen アドレスの落とし穴(127.0.0.1 問題)
結論:
127.0.0.1:ポートで待ち受けるサービスは、同じサーバ内からは繋がるが外部からは必ず refused になる。ss -tlnpの Local Address 列が127.0.0.1か0.0.0.0(または::)かを必ず確認する。
これは「サービスは動いているのに外から繋がらない」典型パターンだ。先ほどの出力をもう一度見る。
LISTEN 0 128 0.0.0.0:22 ... sshd LISTEN 0 511 127.0.0.1:8080 ... node
0.0.0.0:22→ 全インターフェースで待ち受け。外部から接続可能。127.0.0.1:8080→ ループバックのみ。サーバ内curl localhost:8080は通るが、外部や別ホストからは refused。
サーバ内では成功し、外からは refused になるなら、ほぼこれが原因だ。アプリの待ち受けアドレス設定(多くは bind / listen / host 等の項目)を 0.0.0.0 や対象 IP に変更し、再起動する。
# サーバ内で成功(ローカルなので届く) $ curl -sS http://127.0.0.1:8080/ >/dev/null && echo OK # 外部・別ホストからは refused になる $ curl http://192.0.2.10:8080/ curl: (7) Failed to connect ... Connection refused
DB(PostgreSQL / MySQL 等)はデフォルトで 127.0.0.1 待ち受けの製品が多い。外部接続させる設計変更は、認証・ファイアウォールの見直しとセットで行う。安易に 0.0.0.0 へ開けない。
ファイアウォールが原因の場合(REJECT と DROP)
結論: ファイアウォールが
REJECTだと RST/ICMP を返すため Connection refused に、DROPだとパケットを捨てるため timeout になる。refused が出るなら REJECT ルールの有無を疑う。
ファイアウォールの拒否方法は 2 種類あり、クライアントに出る症状が変わる。
- REJECT: 拒否を明示的に通知(TCP RST または ICMP port-unreachable)→ クライアントは即
Connection refused - DROP: 黙ってパケットを破棄 → クライアントは応答待ちの末
timeout
つまり refused が出ている時点で、DROP 型のファイアウォール(ufw のデフォルト deny は DROP)は主犯ではないことが多い。それでも明示 REJECT ルールが入っていないかは確認する。
# nftables のルール確認 $ sudo nft list ruleset | grep -i -E 'reject|dport' # iptables を使う環境 $ sudo iptables -L -n -v --line-numbers
Chain INPUT (policy ACCEPT) num target prot ... destination 1 REJECT tcp ... tcp dpt:8080 reject-with tcp-reset
上記のように REJECT ... reject-with tcp-reset が該当ポートに入っていれば、それが refused の発生源だ。ルールの要否を判断し、不要なら削除する。ufw を使っているなら許可ルールの確認と復旧手順は ufw でSSHが繋がらない時を参照。
切り分けの近道: サーバ内のループバック接続(curl 127.0.0.1:ポート)が通り、外部だけ refused なら、原因は Listen アドレスかファイアウォール。サーバ内でも refused なら、サービス停止かポート違いに絞れる。
クライアント側からの切り分け手順
結論: サーバに入れないときは
nc -zvで疎通だけを確認し、refused か timeout かを切り分ける。ss -tnで接続が確立まで進んでいるかも見える。
サーバにすぐ入れない状況では、クライアント側から最小限の確認をする。
# ポートに繋がるかだけを確認(-z: 送信せず接続のみ, -v: 詳細) $ nc -zv 192.0.2.10 22
Connection refused が即返れば、ホストは応答しているがポートが閉じている。timed out なら経路かファイアウォール DROP を疑い、本記事ではなく ポート疎通の確認へ。
接続を試みた後の TCP 状態を見ると、ハンドシェイクがどこまで進んだか分かる。
$ ss -tn dst 192.0.2.10
SYN-SENT のまま留まるなら応答が返っていない(timeout 系)。refused では接続自体が成立せず、即座に失敗する。
ポートスキャンや疎通確認は、自分が管理する、または許可を得たホストに対してのみ行う。第三者のホストへの無断スキャンは避ける。
それでも直らないときのチェックリスト
結論: refused は「ホスト生存・ポート拒否」が確定した状態。サービス → ポート → Listen アドレス → REJECT ルールの順に潰せば、原因はほぼこの 4 つのどれかに収束する。
- [ ] エラーは
refused(即時)かtimed out(待たされる)か区別したか - [ ] サーバ上で
ss -tlnpし、目的ポートが LISTEN にあるか確認したか - [ ] LISTEN が無いなら
systemctl status/journalctl -uで停止理由を見たか - [ ] Local Address が
127.0.0.1になっていないか(外部接続なら0.0.0.0等が必要) - [ ] ポート番号は接続側と待ち受け側で一致しているか
- [ ]
nft list ruleset/iptables -Lに該当ポートの REJECT ルールが無いか - [ ] サーバ内ループバック(
curl 127.0.0.1:ポート)は通るか(通れば原因は外側)