結論
connection refused は PostgreSQL が生成した文言ではありません。接続先の OS が接続要求を拒んだときの ECONNREFUSED を、libpq が strerror() の結果としてそのまま転記したものです。実装では、接続に失敗した箇所で SOCK_STRERROR(errorno, ...) の結果を先頭に置き、その後ろに助言の1行を足しています。
この由来から、確認すべき範囲がほぼ確定します。要求は指定した相手とポートまで届いており、そこで待ち受けている相手がいなかったという意味です。したがって、パスワード、pg_hba.conf、ロール、データベース名は一切関係ありません。これらの問題であれば、接続自体は成立したうえで PostgreSQL 側の FATAL が返ります。
見るべきは3つです。サーバーが動いているか、待ち受けている住所に自分が届いているか、ポート番号が合っているかです。
エラーが発生する処理段階
クライアントが接続を確立するまでには段階があります。connection refused は最初の段階、つまり connect() の呼び出しで止まっています。
第一段階は接続先の決定です。libpq は host から名前を引き、hostaddr があればそちらを使います。第二段階が実際の接続で、ここで拒否されると connection refused になります。第三段階が起動時のやり取りで、データベース名と利用者名を送ります。第四段階が認証で、pg_hba.conf の照合とパスワードの確認が行われます。
第二段階で止まっているということは、PostgreSQL のプロセスがこの要求を一度も見ていないということです。サーバーのログにも何も残りません。ログを探しても記録が無いのは異常ではなく、この段階で止まっている証拠です。
最初に確認すること
まず、どの相手のどのポートに対して拒否されたのかを、エラー文からそのまま読み取ります。
psql: error: connection to server at "localhost" (::1), port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
括弧の中は、名前を引いた結果の実際の住所です。ここが ::1 になっているのに待ち受けが IPv4 だけ、という食い違いはよく起きます。localhost は環境によって IPv6 を先に返すためです。
次に、そのポートで待ち受けている相手がいるかを確認します。
ss -ltnp | grep 5432
行が1つも出なければ、サーバーが動いていないか、別のポートで待ち受けています。行が出た場合は、左側の住所欄を見てください。127.0.0.1:5432 ならネットワーク越しの接続は拒否され、0.0.0.0:5432 または *:5432 なら受け付けます。
原因別の確認方法と解決策
原因1:サーバーが起動していない
最も多い形です。停止しているか、起動に失敗して落ちています。
確認方法は稼働状態の照会です。
sudo systemctl status postgresql
停止していれば inactive または failed と出ます。failed の場合は起動を試す前に、失敗の理由を先に読んでください。設定ファイルの誤りで落ちている場合、起動をやり直しても同じ場所で止まります。
sudo journalctl -u postgresql --since "1 hour ago" | tail -40
理由が分かってから起動します。
sudo systemctl start postgresql
原因2:待ち受けの住所に自分が含まれていない
サーバーは動いているのに、ネットワーク越しからだけ拒否される場合です。listen_addresses の既定値は localhost で、公式ドキュメントには、この値ではサーバー自身からの折り返し接続だけが許されると明記されています。
確認方法は現在値の照会です。手元から接続できるなら、こちらが確実です。
SHOW listen_addresses;
接続できない場合は設定ファイルを直接読みます。
grep -n "^listen_addresses" /etc/postgresql/*/main/postgresql.conf
対処は、受け付けたい範囲だけを列挙することです。* はすべてのネットワーク接続面で待ち受けるという意味なので、外部に露出するサーバーでは慎重に扱ってください。公式ドキュメントも、この値が誰を通すかではなく、どの接続面で接続要求を受け付けるかを決めるものであり、安全でない接続面での攻撃を防ぐ役割があると説明しています。開放する場合は、pg_hba.conf とファイアウォールの制限を先に用意してください。
listen_addresses = '10.0.1.5, localhost'
この値は起動時にしか変更できません。反映には再起動が要ります。
原因3:ポート番号が合っていない
既定は 5432 です。公式ドキュメントにも、待ち受ける TCP ポートは既定で 5432 だと書かれています。複数の版を同居させている環境では 5433 や 5434 が割り当てられていることがあり、接続側だけが既定のままになっている、という食い違いが起きます。
確認方法は両側の突き合わせです。
sudo -u postgres psql -c "SHOW port;"
echo "$PGPORT"
対処は接続側を合わせることです。環境変数か接続文字列で指定します。
psql "host=10.0.1.5 port=5433 dbname=app user=app"
原因4:ファイアウォールが拒否を返している
ここは切り分けの分かれ目になります。ファイアウォールが拒否の応答を返す設定なら connection refused になり、応答せずに破棄する設定なら応答が返らないため待ち続けて時間切れになります。つまり connection refused が出ている時点で、破棄型の遮断は候補から外れます。
確認方法は、経路上の応答を見ることです。
nc -vz 10.0.1.5 5432
Connection refused と返れば相手まで届いています。応答が無いまま止まるなら、破棄型の遮断か経路の問題です。
対処は必要な範囲だけを開けることです。全体を開放せず、接続元を限定してください。
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.1.0/24" port port="5432" protocol="tcp" accept'
sudo firewall-cmd --reload
原因5:コンテナの中から自分自身を指している
コンテナで動かしている場合、localhost はそのコンテナ自身を指します。データベースが別のコンテナにあるなら、そこには誰も待ち受けていません。
確認方法は、接続元の中から見た到達性です。
docker compose exec app getent hosts db
名前が引けなければ、同じネットワークに属していません。引ける場合は、その名前を接続先に使います。
docker compose exec app psql "host=db port=5432 dbname=app user=app"
なお、公開設定で 127.0.0.1:5432:5432 としている場合、外部からは拒否されます。コンテナの外に出す必要があるかどうかを先に決めてください。
近いエラーとの境界
could not connect to server: No such file or directory は、TCP ではなく Unix ドメインソケットで接続しようとして、指定した経路にソケットが無い場合です。libpq は接続先がソケットのとき、助言の文言を「そのソケットで待ち受けているか」に切り替えます。助言の1行を読めば、TCP とソケットのどちらで試したかが分かります。
応答が返らないまま時間切れになる場合は、要求が相手に届いていません。経路の遮断か、住所そのものの誤りです。connection refused とは逆に、相手まで届いていないことを示します。
no pg_hba.conf entry for host は接続が成立したあとの認証段階です。要求は PostgreSQL に届いており、照合の規則に合う行が無かったという意味になります。
password authentication failed for user も認証段階です。行の照合までは通り、パスワードの確認で落ちています。
FATAL: database "app" does not exist や FATAL: role "app" does not exist は、さらに後の段階です。いずれも接続は成立しています。
判断の目安は単純です。FATAL で始まる文言が返っていれば PostgreSQL が応答しており、connection refused なら応答していません。
内部動作または公式仕様
libpq のエラー文は2つの部品を連結して作られます。前半は接続先を特定する部分で、TCP なら connection to server at "%s" (%s), port %s failed: の書式です。名前と、引いた結果の住所が異なる場合だけ括弧の中に住所が入ります。Unix ドメインソケットの場合は connection to server on socket "%s" failed: になります。
後半は失敗の理由で、connectFailureMessage() が OS の strerror() の結果をそのまま置きます。Connection refused はここに入る文字列です。実装のコメントには、この関数が「向こうにサーバーがいないことを示す場合」に使うものだと明記されています。
助言の1行も、接続先の種類で切り替わります。TCP なら Is the server running on that host and accepting TCP/IP connections?、ソケットなら Is the server running locally and accepting connections on that socket? です。
listen_addresses については、公式ドキュメントが役割の違いを明示しています。誰が接続してよいかを決めるのは pg_hba.conf の側で、listen_addresses はどの接続面が接続要求を受け付けるかを決めます。前者が通す設定でも、後者に含まれていなければ要求は届きません。
バージョン差・注意点
エラー文の書式は PostgreSQL 14 で変わりました。13 以前は could not connect to server: Connection refused で始まり、助言の中に対象のサーバー名とポートが埋め込まれる形でした。14 以降は接続先が先頭に来て、理由が後ろに続きます。実装を版ごとに比べると、13 系までは旧書式、14 系から新書式になっています。検索した記事の書式が手元と違う場合は、この境目を疑ってください。
助言の文言に住所が含まれるかどうかも変わっています。旧書式は助言の中に住所とポートを入れていましたが、新書式では先頭に移りました。古い手順書をそのまま使うと、読む場所を間違えます。
対処の面では、listen_addresses を * にする案が広く出回っていますが、これは全接続面での待ち受けを意味します。到達できる範囲が広がるため、pg_hba.conf の内容とファイアウォールの設定を確認してから行ってください。設定を緩めるより先に、経路とポートの食い違いを潰すほうが安全です。
Editor’s Note
PostgreSQL 14 における接続失敗メッセージの書式変更は、このエラーの調査手順に直接影響します。14 のリリースは2021年9月30日で、この版から connection to server at "host" (address), port N failed: の書式になりました。13 以前は could not connect to server: で始まります。
当時の状態としては、旧書式が長く使われていたため、日本語で書かれた解説の多くが旧書式を前提にしています。助言の中に住所とポートが入る前提で「2行目を読め」と書かれた手順は、14 以降では成立しません。
現在も適用できるかという点では、書式の違いはそのまま残っています。実装を確認すると、13 系のソースには旧書式が、14 系以降には新書式が入っています。手元の版は psql --version で確認できます。読み替えの規則は単純で、旧書式では2行目に住所とポート、新書式では1行目に入っている、という対応です。
参考資料
- Connections and Authentication(listen_addresses、port)
- libpq - Database Connection Control Functions
- The pg_hba.conf File
- PostgreSQL 14 Release Notes
- libpq の実装(fe-connect.c)
免責事項:本記事の内容は、執筆時点の公開情報をもとに作成したものです。ソフトウェアの仕様は予告なく変更されることがあります。最新の情報は各ツールの公式サポートページをご確認ください。本記事の情報を利用した結果生じたいかなる損害についても、著者および運営者は責任を負いかねます。
この記事でエラーは解決しましたか?