シナリオ:60秒では足りない場合ブラウザに表示される504 Gateway Timeoutは、単なる症状に過ぎません。本当の原因はNginxのエラーログに隠されています。先日、50MBのPDFファイルをエクスポートするレポート機能を構築していた際、この問題に遭遇しました。小さなレポートは即座に動作しましたが、データセットが5,000行を超えた途端、リクエストがちょうど60秒間ハングし、その後エラーになりました。
/var/log/nginx/error.logを詳しく確認したところ、次のような問題が見つかりました:
[error] 1234#0: *567 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 1.2.3.4, server: example.com, request: "POST /api/export HTTP/1.1", upstream: "http://127.0.0.1:8000/api/export"
これは、Nginxがバックエンド(PHP-FPM、Gunicorn、Node.jsなど)に接続してリクエストを送信したものの、タイムアウトまでにバックエンドからレスポンスヘッダーが1バイトも返ってこなかったことを意味します。
分析:60秒の壁Nginxは忍耐強いですが、それも一定の範囲内です。デフォルトでは、アップストリーム(バックエンド)からのレスポンスを60秒間待ちます。アプリが巨大なデータベースクエリを処理していたり、低速なサードパーティAPIからデータを取得していたりすると、この壁に突き当たります。「110: Connection timed out」エラーは、特定のネットワーク信号です。これは、Nginxとアプリケーション間の内部接続は維持されていたものの、長時間にわたり応答がなかったことを示しています。
即効性のある修正:タイムアウトの延長Nginxに余裕を持たせる必要があります。設定すべきディレクティブは、Nginxがアプリとどのように通信しているかによって異なります。負荷の高いエンドポイントに対して、私は通常これらを300秒(5分)に設定します。
リバースプロキシ(Node.js、Gunicorn、Go)の場合proxy_passを使用している場合は、locationブロックを更新します。これら3つのタイムアウト値を増やすことで、リクエストのどのフェーズでも接続が切れないようにします。
location /api/ {
proxy_pass http://localhost:8000;
proxy_read_timeout 300s;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
}
PHP-FPM (FastCGI) の場合PHPのセットアップではFastCGIプロトコルを使用します。ここではfastcgi_read_timeoutディレクティブが必要です。これは、実行時間の長いLaravelやWordPressのスクリプトで一般的です。
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_read_timeout 300s;
}
Python/UWSGI の場合```
location / { include uwsgi_params; uwsgi_pass unix:/tmp/app.sock; uwsgi_read_timeout 300s; }
Nginxをリロードして変更を適用します:
sudo nginx -t && sudo systemctl reload nginx
## 見落としがちな点:バックエンドの設定Nginxの設定変更は、解決策の半分に過ぎません。バックエンド側に独自の30秒タイムアウトが設定されている場合、Nginxが待ち終わる前にバックエンド側でプロセスが終了してしまいます。これらの制限を同期させる必要があります。
### PHP-FPMの更新より長い実行時間を許可するように`php.ini`を編集します:
max_execution_time = 300
また、`/etc/php/8.2/fpm/pool.d/www.conf`に以下の設定があるか確認してください:
request_terminate_timeout = 300
### Gunicorn (Python) の更新Gunicornのデフォルトはわずか30秒のタイムアウトです。これを変更しないと、Gunicornがワーカーを強制終了した際に、Nginxは「Bad Gateway」やタイムアウトを報告します。次のように起動オプションを指定してください:
gunicorn --timeout 300 myapp.wsgi:application
## 検証:新しい制限をテストする設定を盲信せず、`curl`を使用して、サーバーが実際にどのくらいの時間接続を保持するかを確認します:
time curl -I http://example.com/api/heavy-task
出力される「real」の時間を確認してください。60秒を超えて`200 OK`が返ってくれば、修正は成功です。300秒経過しても失敗する場合は、そのタスクが標準的なウェブリクエストとしてはあまりに重すぎる可能性があります。
## ベストプラクティスタイムアウト時間を増やすのはあくまで応急処置です。ユーザーは5分間もローディング画面を見続けることを嫌います。非常に重いタスクの場合は、ロジックをCeleryやRedis Queueのようなバックグラウンドワーカーに移動させましょう。ユーザーには即座に「ジョブを開始しました」というメッセージを返し、後で結果をポーリング(確認)させる仕組みにします。構築は複雑になりますが、そのほうがはるかに信頼性が高く、ユーザー体験も向上します。

