接続が拒否される理由
Pythonが ConnectionRefusedError: [Errno 111] をスローしたとき、スクリプトは実質的に「虚空に向かって叫んでいる」状態です。これは、コードがターゲットのIPアドレスに正常に到達したものの、送信先のオペレーティングシステムが「拒否」という明確な応答を返したことを意味します。その特定のポートでリクエストを待ち受けている(リスニングしている)サービスが存在しません。
これはタイムアウトとは異なります。タイムアウトでは、リクエストが失われるか無視されます。一方、接続拒否(Refusal)の場合、TCPレベルで能動的かつ即座に拒否されます。Linuxでは Errno 111 が使用されますが、Windowsの開発者は全く同じ問題に対して WinError 10061 を目にすることになります。
よくある原因
90%の場合、このエラーは以下の4つのシナリオのいずれかに集約されます。
- 幽霊サービス: データベース、Redisインスタンス、またはWebサーバーが実際には起動していません。
- ポートの不一致: ポート
5432を叩いていますが、データベースは先週5433に移動されています。 - ローカルホストの罠: サービスは
127.0.0.1でリスニングしていますが、別のコンテナや外部IPから接続しようとしています。 - 能動的なブロック:
ufwやiptablesなどのファイアウォールがパケットを明示的に破棄しています。
ステップバイステップの解決策
1. サービスが起動しているか確認する
バックグラウンドタスクが実行されていると思い込まないでください。まずはサーバー上で直接、ターゲットサービスのステータスを確認することから始めましょう。
# システムレベルのサービスを確認する
sudo systemctl status redis-server
# Docker環境の場合
docker ps | grep my_database_container
サービスがアクティブでない場合は、再起動してください。クラッシュを繰り返す場合は、/var/log/ にあるログを調査して、根本的な失敗の原因を特定してください。
2. リスニングポートを探す
サービスが「起動」していても、期待通りの場所で待ち受けていない可能性があります。ss ツールを使用して、実際にどのプロセスがポートを開いているかを確認します。
# すべてのアクティブなTCPリスナーを表示
sudo ss -tulpn | grep LISTEN
出力から該当するポート(例:Redisなら :6379)を探します。Pythonコードが 8080 をターゲットにしているのに、ss が 8081 でサービスを表示している場合は、すぐに接続文字列を更新してください。
3. localhost(127.0.0.1)の制限を解除する
これは現代のマイクロサービスにおいて最も頻繁に遭遇する障害です。サービスが 127.0.0.1 にバインドされている場合、自分自身としか通信できません。外部からのリクエストは一切受け付けません。
PythonスクリプトがDockerコンテナ内にあり、localhost:6379 のRedisにアクセスしようとしている場面を想像してください。これは失敗します。コンテナ内部において「localhost」はその特定のコンテナのループバックを指し、ホストマシンやRedisサービスを指すわけではないからです。
解決策: サービスが 0.0.0.0(すべてのインターフェース)でリスニングするように設定します。カスタムのPythonソケットサーバーの場合は、bindの呼び出しを更新します:
# ループバックに制限する代わりに:
# sock.bind(('127.0.0.1', 8080))
# ネットワーク全体に開放する:
sock.bind(('0.0.0.0', 8080))
4. データベース固有の権限を確認する
データベースには独自の内部ゲートキーパーが存在することがよくあります。OSが接続を許可していても、設定に基づいてDB側が拒否することがあります。
- PostgreSQL:
postgresql.confを開き、listen_addressesが'*'に設定されていることを確認してください。次に、pg_hba.confでIPの許可設定を確認します。 - Redis:
redis.confを確認してください。ネットワーク越しに接続する場合は、protected-modeがnoになっていることを確認します。 - MySQL:
my.cnfのbind-address = 127.0.0.1を探し、0.0.0.0に変更します。
5. ファイアウォールとセキュリティグループを監査する
サービスが実行されており、0.0.0.0 でリスニングしているにもかかわらず Errno 111 が発生する場合、ファイアウォールが原因である可能性が高いです。Linuxでは、明示的にポートを許可します。
# Ubuntu/Debianでポート8080を開放する
sudo ufw allow 8080/tcp
AWSやGCPにデプロイしている場合は、インバウンドセキュリティグループルールを再確認してください。スクリプトのIPアドレス(またはVPC範囲)が、その特定のポートへのアクセスを許可されているか確認します。
経路をテストする
Pythonアプリを再起動する前に、専用のネットワークツールを使用しましょう。これにより、Pythonのロジックを介さずに、経路がクリアであることを証明できます。
# netcatを使用してポートを確認する
nc -vz 192.168.1.50 8080
接続に成功すると succeeded! と返されます。依然として Connection refused と表示される場合、問題はコードではなく、間違いなくサーバーの設定かネットワークにあります。
予防のためのプロのヒント
複雑なサブネットやVPCを越えたネットワーキングは、非常に厄介なことで知られています。新しいクラスターを構築する際、私は IPサブネット計算機 を使用してCIDRブロックをマッピングします。これにより、IPの重複を防ぎ、最初から正確なファイアウォールルールを書きやすくなります。
また、localhost をハードコードするのは避けましょう。DB_HOST のような環境変数を使用してください。これにより、コードを一切変更することなく、開発時の localhost から本番環境の db.internal.network へ切り替えることが可能になります。
最後に
Errno 111 がPythonロジックのバグであることは稀です。ほとんどの場合、設定の不一致です。Pythonスクリプトを書き直す前に、必ずサービスが実行されているかを確認し、正しいインターフェース(0.0.0.0)でリスニングしていることを保証し、nc を使用して経路が開放されていることを検証してください。

