エラーメッセージ
net::ERR_EMPTY_RESPONSE
Chromeで真っ白な画面が表示されています。DevToolsのNetworkタブにはステータス「(failed)」が表示され、コンソールには「net::ERR_EMPTY_RESPONSE」と出力されます。これは、サーバーがエラー内容を説明してくれる標準的な404や500エラーとは異なります。その代わりに、サーバーは一言も発する前に電話を切ってしまったような状態です。TCP接続は確立されたものの、実際のHTTPレスポンスの代わりにFINまたはRSTパケットが送信されています。
本番サーバーでは、通常、ロードバランサーとWebサーバー間の不整合が原因です。また、バックエンドプロセスのクラッシュや、転送中のネットワークパケットのドロップを意味することもあります。
ステップ1:NginxのKeep-Alive設定を調整する
ほとんどのERR_EMPTY_RESPONSEは、keep-aliveタイムアウトの競合に起因します。例えば、AWS ALBやCloudflareが接続を60秒間維持することを想定しているのに、Nginxサーバーが5秒で接続を切断した場合、ブラウザはすでに存在しない接続を再利用しようとしてしまいます。
通常は /etc/nginx/nginx.conf にあるNginxの設定ファイルを開き、以下の値を確認してください:
http {
# ロードバランサーのタイムアウトより高い値に設定してください
keepalive_timeout 75;
keepalive_requests 1000;
}
keepalive_timeout は、常に前段にあるプロキシの設定よりも5〜10秒ほど長く設定してください。Nginxをリバースプロキシとして使用している場合は、バックエンドがリクエストを途中で切断しないよう、アップストリームのkeep-aliveも調整する必要があります:
upstream backend_servers {
server 127.0.0.1:8080;
keepalive 32;
}
ステップ2:アップストリームの読み取りタイムアウトを延長する
Node.jsやPHPのバックエンドが重いレポートの処理に61秒かかるのに、Nginxの制限が60秒になっている場合、接続は切断されます。Nginxが504 Gateway Timeoutエラーを生成する前に接続が切れてしまうため、空のレスポンスが発生することがよくあります。
location ブロック内のこれらの値を300秒に増やして、エラーが解消されるか確認してください:
location / {
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_pass http://backend_servers;
}
ステップ3:バックエンドのクラッシュを特定する
Nginx自体は正常でも、データ処理中にバックエンドサービスが停止してしまうことがあります。PHP-FPMがセグメンテーション違反を起こしたり、Node.jsプロセスが1GBのRAM制限を超えたりすると、ソケットは即座に切断されます。サーバーは送信するものがなくなるため、何も送信しません。
ログで特定の終了コードを確認してください:
- **PHP-FPM:** `tail -f /var/log/php8.1-fpm.log` を実行し、「SIGSEGV」(シグナル11)を探します。
- **Node.js:** `journalctl -u my-app -f` を使用するか、PM2のログを確認します。「Out of Memory」(OOM)エラーを探してください。
ヘッダーを送信する前にプロセスが終了している場合、継続的にこの空のレスポンスエラーが発生します。
ステップ4:MTUとネットワークフラグメンテーションを修正する
クラウド環境やVPN経由では、Maximum Transmission Unit (MTU) が問題を引き起こすことがあります。サーバーが1500バイトのパケットを送信しても、ネットワークホップで1400バイトしか許可されていない場合、「Don't Fragment」ビットが設定されているとパケットが破棄される可能性があります。ブラウザは届かないデータを待ち続け、最終的にタイムアウトします。
フラグメンテーションを確認するために、クイックpingテストを実行します:
# 標準的なパケットサイズを試す
ping -M do -s 1472 yourserver.com
# 失敗した場合は、より小さいサイズを試す
ping -M do -s 1372 yourserver.com
小さいサイズなら通るのに大きいサイズだと通らない場合は、MTUがボトルネックになっています。修正を確認するために、一時的にインターフェースのMTUを1400に下げることができます:
sudo ip link set dev eth0 mtu 1400
ステップ5:セキュリティによる遮断を調査する
ModSecurityやFail2Banなどのファイアウォールは、動作が強力すぎる場合があります。通常のNginxによる拒否は403 Forbiddenを返しますが、Deep Packet Inspection (DPI) ファイアウォールは、攻撃を疑うとTCPセグメント自体を完全に切断することがあります。これは、大きなPOSTペイロードや特定のSQLキーワードを送信する際によく見られます。
Webアプリケーションファイアウォール (WAF) を60秒間無効にしてみてください。エラーが止まる場合は、サーバーコードではなくセキュリティルールの調整が必要です。
検証
ローカルキャッシュによって実際の結果が隠れてしまう可能性があるため、ブラウザの更新だけを頼りにしないでください。curl を使用して、生のハンドシェイクを確認します:
curl -v https://your-site.com/api/data
* Empty reply from server と表示される場合は、まだ接続が切断されています。修正が成功すると、< HTTP/1.1 200 OK に続いて実際のデータペイロードが表示されます。
予防とベストプラクティス
ネットワークの不具合は、IP範囲の重複や複雑なサブネット化に潜んでいることがよくあります。マイクロサービスを構築する際は、ルーティングループを避けるためにCIDRブロックを再確認してください。私はネットワークの境界をマッピングするために、よく ToolCraftのサブネット計算機 を使用します。これにより、単純な2進数の計算ミスによる深夜2時の頭痛を防ぐことができます。
最後に、ファイル記述子(file descriptor)の制限を確認してください。サーバーが高トラフィックを処理する場合、Nginxが worker_connections の制限に達する可能性があります。ulimit -n を実行して、現在の制限を確認してください。もしデフォルトの1024のままであれば、負荷がかかった時にOSが接続を強制終了しないよう、 /etc/security/limits.conf で65535に引き上げてください。

