「Connection refused」の切り分け - サービス停止かポート閉塞か

「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 statusjournalctl で起動状態と理由を裏取りする。

まずサーバにログインし、待ち受けているポート一覧を見る。

$ 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: failedinactive (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.10.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:ポート)は通るか(通れば原因は外側)

次に読む