Nginx 504 Gateway Timeout とクライアントタイムアウトの問題を解決する

Nginxのタイムアウト、特に厄介な504 Gateway Timeoutをマスターするために、重要なプロキシディレクティブの調整方法を学びます。このガイドでは、`proxy_read_timeout`の増加、バッファリングの最適化、エラーログを使用したNginxとアップストリームサーバー間の通信障害の診断方法を詳しく説明し、堅牢な接続処理を実現します。

Nginx 504 Gateway Timeout とクライアントタイムアウトの問題を解決する

Nginxの504 Gateway Timeoutは、Nginxがプロキシまたはゲートウェイとして動作している際に、アップストリームサービスから時間内に応答が得られなかったことを意味します。アップストリームは、Node.jsアプリ、Gunicorn、PHP-FPM、別のNginxサービス、内部API、ロードバランサーなどです。

誘惑されるのは、すべてのタイムアウトを5分に引き上げて先に進むことです。特にレポート、インポート、管理専用ジョブなどでは、より長いタイムアウトが短期的な正しい対応となる場合もあります。しかし、通常のユーザーリクエストがNginxの現在の許容時間よりも長くかかる場合は、バックエンドが遅い理由、リクエストを非同期にすべきかどうか、パス上の別のプロキシのタイムアウトがより短く設定されていないかどうかも問い直す必要があります。


504 Gateway Timeout エラーを理解する

504 Gateway Timeoutエラーは、Nginxがリバースプロキシまたはゲートウェイとして動作している際に、リクエストを転送したアップストリームサーバーからタイムリーな応答を受け取れなかった場合に発生します。簡単に言うと、Nginxがバックエンドに応答を求め、設定された時間待機しましたが、応答が届かなかったため諦めたということです。

これは、Nginxが無効な応答や早期に閉じられたアップストリーム応答を受け取った502 Bad Gatewayや、サービスが意図的に利用不可または過負荷であることを示すことが多い503 Service Unavailableとは異なります。この違いは重要であり、504は待機、タイミング、アップストリームのレイテンシに問題があることを示しています。

アップストリームタイムアウトを制御する主要ディレクティブ

リクエストをプロキシする際、Nginxはいくつかの重要なディレクティブを使用します。これらは主にhttpserverlocationブロック内、または特にupstreamブロック内に配置されます。これらの値を調整することが、504エラーを解決するための主要な方法です。

1. proxy_connect_timeout

これは、アップストリームサーバーとの接続を確立するためのタイムアウトを設定します。Nginxがこの期間内に接続できない場合、タイムアウトエラーを返します。

デフォルト: 60秒

proxy_connect_timeout 60s;

2. proxy_send_timeout

これは、アップストリームサーバーへの連続した2回の書き込み操作の間のタイムアウトを設定します。これは、大きなリクエストボディを送信する場合に関連します。

デフォルト: 60秒

proxy_send_timeout 60s;

3. proxy_read_timeout(504の最も一般的な修正)

これは、リクエストヘッダーが送信された後、アップストリームサーバーからの応答を待つためのタイムアウトを設定します。バックエンドアプリケーションがリクエストの処理と応答ボディの生成に時間がかかりすぎる場合、このディレクティブを増やす必要があります。

デフォルト: 60秒

# 例: 遅いAPIのために読み取りタイムアウトを120秒に増やす
proxy_read_timeout 120s;

アプリケーションがデフォルトを頻繁に超える場合は、慎重にこの値を増やし、調査を続けてください。非常に高い値は、バックエンドがすでに異常であるにもかかわらず、クライアント接続を開いたままにする可能性があります。


クライアント側のタイムアウトへの対処

クライアント側のタイムアウトは別の障害です。ブラウザ、モバイルアプリ、ロードバランサー、CDN、呼び出し元サービスが、Nginxが応答を完了する前に諦めます。その場合、ユーザーはブラウザエラーやNginxの前のレイヤーからのゲートウェイエラーを目にする可能性があり、Nginxはクリーンな504ではなく、閉じられた接続をログに記録する可能性があります。

Nginxが504をログに記録する前にクライアントタイムアウトが発生している場合は、クライアントとNginxの間の接続を確認する必要があります。

1. クライアント側のキープアライブ

クライアントが早期に接続を閉じた場合、Nginxはエラーを受け取るか、クライアントが単にデータを待ってタイムアウトする可能性があります。

クライアントが別のプロキシやロードバランサーの場合は、そのタイムアウト設定をNginxとバックエンドに対して確認してください。チェーン内の最も短いタイムアウトが通常優先されます。一般的なパターンは次のとおりです。CDNは100秒待機、ロードバランサーは60秒待機、Nginxは180秒待機、バックエンドは120秒かかります。ロードバランサーが最初に諦めるため、ユーザーは60秒で失敗します。

2. Nginx send_timeout

このディレクティブは、Nginxがクライアントからの確認応答またはデータ受信を待つ時間(クライアントへの連続した2回の書き込み操作の間の時間)を制御します。

デフォルト: 60秒

# Nginxが応答を送信している間にクライアントがタイムアウトする場合に設定
send_timeout 120s;

大きな応答に対するバッファリングの最適化

バックエンドが応答を開始しても、応答が巨大である、クライアントが遅い、またはNginxが予想以上にバッファリングする必要があるために、配信が依然として遅い場合があります。これは、生成されたCSVエクスポート、アプリ経由でルーティングされるメディアダウンロード、または非常に大きなJSONペイロードを返すAPIで一般的です。

Nginxは、アップストリームから受信したデータをクライアントに送信する前に一時的に保持するためにバッファを使用します。応答が非常に大きい場合、これらのバッファが超過され、複雑な処理や認識されるレイテンシが発生する可能性があります。

主要なバッファリングディレクティブ

これらは通常、locationブロックまたはserverブロック内に設定されます。

ディレクティブ 目的
proxy_buffers アップストリームからの応答を読み取るために使用されるバッファの数とサイズを設定します。形式: 数値 サイズ;
proxy_buffer_size 応答ヘッダーを読み取るために使用される最初のバッファのサイズを設定します。
proxy_max_temp_file_size 応答が利用可能なバッファを超える場合、Nginxは一時ファイルに書き込みます。これは、これらの一時ファイルの最大サイズを設定します。

高ボリューム/大規模応答の設定例:

location /api/heavy_report {
    proxy_pass http://backend_app;

    # 読み取りタイムアウトを増やす
    proxy_read_timeout 180s;

    # 潜在的に大きな応答ボディのためのバッファリングを調整
    # 8つのバッファを使用し、それぞれ最大1MB(1024k)
    proxy_buffers 8 1024k;
    proxy_buffer_size 256k;

    # バッファがオーバーフローした場合、最大500MBの一時ファイルを許可
    proxy_max_temp_file_size 500m;
}

バックエンドの応答が本当に巨大な場合は、アプリを通じてリクエストを開いたままにする代わりに、オブジェクトストレージや静的ストレージから生成されたファイルを提供することを検討してください。エクスポートの場合、一般的なパターンは次のとおりです。ジョブをキューに入れ、ファイルを生成し、準備ができたらユーザーが静的URLからダウンロードできるようにします。


トラブルシューティングの手順とログ分析

タイムアウトを解決するには、停止が発生した場所(クライアント -> Nginx、またはNginx -> バックエンド)を特定する必要があります。

ステップ1: Nginxエラーログを確認する

Nginxエラーログは、Nginxがバックエンドを待ってタイムアウトしたかどうかを判断するための決定的な情報源です。

次のようなフレーズを含むエントリを探してください。

  • upstream timed out (110: Connection timed out)
  • upstream prematurely closed connection while reading response header from upstream

これらが見つかった場合、問題はproxy_read_timeoutまたはバックエンドの処理時間にあります。

また、client prematurely closed connectionを探してください。これは通常、クライアントまたはNginxの前のプロキシが最初に諦めたことを意味します。その場合、proxy_read_timeoutを上げるだけではユーザーを助けられません。

ステップ2: バックエンドアプリケーションのログを確認する

Nginxがタイムアウトした場合(ログが504を示す)、すぐにアップストリームサービス(例:PHP-FPMログ、Gunicornログ、Javaアプリケーションサーバーログ)のログを確認してください。リクエストがバックエンドに到達したかどうか、そして完了までにどのくらいの時間がかかったかを確認する必要があります。

  • バックエンドログが、リクエストに設定されたproxy_read_timeoutより長い時間がかかったことを示している場合は、Nginxのタイムアウトを増やしてください。
  • バックエンドログがリクエストが迅速に完了したことを示している場合、問題はNginxとバックエンド間のネットワークレイテンシ、またはNginx側のクライアントタイムアウト設定の誤りである可能性があります。

ステップ3: X-Upstream-Response-Time ヘッダーを使用する(オプション)

詳細な診断のために、アクセスログ形式で$upstream_response_time変数を使用して、アップストリームが応答するのにかかった正確な時間をログに記録できます。これにより、バックエンドの実際のパフォーマンスを確認できます。

nginx.conf 内:

log_format proxy_detailed '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" $request_time $upstream_response_time';

access_log /var/log/nginx/access.log proxy_detailed;

$upstream_response_timeを分析することで、Nginxの独自のタイムアウト設定とは無関係に、Nginxが待機した正確な時間を確認できます。

簡単な1回限りのテストとして、Nginxホストからアップストリームを直接呼び出します。

time curl -sS -o /dev/null -w 'status=%{http_code} total=%{time_total}\n' http://127.0.0.1:3000/slow-route

直接のアップストリーム呼び出しがすでに遅い場合、Nginxは問題を報告しているだけです。直接呼び出しが高速であるにもかかわらず、プロキシされたリクエストがタイムアウトする場合は、プロキシ設定、DNS解決、コンテナネットワーキング、内部サービス間のTLS、またはNginxとアプリ間の別のホップを調査してください。


最小限の有用な変更を適用する

合理的な本番環境での修正は、次のようになることがよくあります。

location /api/reports/ {
    proxy_pass http://backend_app;
    proxy_connect_timeout 10s;
    proxy_send_timeout 60s;
    proxy_read_timeout 180s;
}

これにより、読み取りタイムアウトが遅いレポートエンドポイントに対してのみ引き上げられます。サイト上のすべてのリクエストが3分間待機するようにはなりません。ログインルート、チェックアウトルート、ヘルスチェック、パブリックAPIエンドポイントの場合、タイムアウトが長いと、回復する可能性が低いリクエストをクライアントが長く待つことになるため、障害がより厄介になります。

PHP-FPMの場合、同等のものはFastCGIディレクティブを含む場合があります。

location ~ \.php$ {
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    fastcgi_read_timeout 120s;
    include fastcgi_params;
}

PHP、Python、Node.js、アプリケーションサーバー、キュー、データベース、CDN、ロードバランサーはすべて、独自のタイムアウト設定を持つ可能性があることを忘れないでください。Nginxは、バックエンドの独自のワーカータイムアウトがリクエストを強制終了した後も、バックエンドが動作し続けるようにすることはできません。

設定を変更した後(例:タイムアウトの増加やバッファサイズの調整)は、常に設定の構文をテストし、Nginxをリロードしてください。

sudo nginx -t
sudo systemctl reload nginx

次に、同じリクエストを繰り返しながら、Nginxとアップストリームの両方のログを監視します。

sudo tail -f /var/log/nginx/error.log

最善のタイムアウト修正は、変更の明確な理由を残します。このルートは正当に最大2分かかる、このアップストリームは現在一致する制限を持っている、遅いリクエストはログまたはメトリクスで可視化されている、などです。それ以外は一時的なパッチであり、一時的なパッチは設定レビューやインシデントノートでそのようにラベル付けされるべきです。