言語を切り替える
テーマを切り替える

Nginx リバースプロキシ完全ガイド:upstream、バッファ、タイムアウト

午前3時。スマホが狂ったように振動している——本番環境アラート。

ログを確認すると、502 Bad Gatewayが大量発生。バックエンドサービスは落ちていない。しかし、Nginxのタイムアウト設定が短すぎた。トラフィック急増時、リクエスト処理完了前に強制切断されていた。あの proxy_read_timeout 60s の行を見て思った:適当に設定したのが仇になった。

その事故後、1週間かけてNginxリバースプロキシの3つの核心モジュールを徹底的に理解した:upstream負荷分散、proxy bufferバッファ、timeoutタイムアウト設定。正直、この3つが正しく設定できれば、リバースプロキシは10倍のトラフィックを処理できる。設定を間違えると、私みたいに午前3時のアラートで痛い目を見る。

この記事は、私が踏んだ罠、デバッグで学んだ経験、理解した原理を全部まとめたもの。バックエンド、運用やってる人、あるいは単にNginx設定パラメータの背後にあるロジックを理解したい人なら、これで時間を節約できるはず。


Upstream負荷分散:「リクエスト分散」以上の意味

まず基本構文を確認

upstream設定ブロックはNginx負荷分散の核心。基本的な書き方は見たことがあるでしょう:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

単純に見える——バックエンドサーバー群を定義、proxy_passで指向。しかし正直、これだけでは本番環境で不足。実際の運用では考慮すべき点が多い:サーバーが落ちたらどうする?性能の良いマシンに多くのトラフィックを割り振れる?長接続を維持する?

4つの負荷分散アルゴリズム、各シーンに適した選択

Nginxのデフォルトはラウンドロビン(round-robin)——順番に分配。公平だが、賢くない。

バックエンドが長接続シーン——WebSocket、DB接続プール——なら、ラウンドロビンで特定サーバーの接続数が急増する可能性。最小接続(least_conn)アルゴリズムが適切:

upstream backend {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

各サーバーのアクティブ接続数を追跡、新リクエストを最も空いているサーバーに送る。WebSocketでリアルタイムメッセージを push するプロジェクトで、ラウンドロビンだとあるサーバーのメモリが爆発——least_connに変更後、負荷分散が均一になった。

他のシーン:ユーザーがログイン後、以降のリクエストが同じサーバーに当たる必要がある(sessionがローカルに保存)。IP Hashがこれに対応:

upstream backend {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

同じクライアントIPのリクエストは固定バックエンドにハッシュされる。しかし正直、この方法には欠陥——そのサーバーが落ちるとsessionも消える。確実な方法はRedisでsession管理、ip_hashは一時的な対応。

4つ目は一意性ハッシュ(hash)、分散キャッシュシーンでよく使用:

upstream backend {
    hash $request_uri consistent;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

Nginxは weight 単位あたり160個の仮想ノードを作成、リクエストURIをハッシュして特定サーバーに割り当て。メリット:キャッシュヒット率高——同じURIは常に同じマシンに当たる。

重み設定:マシン性能が異なる場合

バックエンドサーバーの性能が異なるのはよくある。32GB RAM、8コアCPUのマシンもあれば、16GB、4コアのみ。公平なラウンドロビン?高性能マシンが無駄になる。

upstream backend {
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 weight=1;
}

weight=3のマシンは3倍のリクエストを受信。性能の良いマシンが多く処理、低性能マシンは少なく——合理的。

backupパラメータ、待機サーバー:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080 backup;
}

backupサーバーは通常の負荷分散に参加しない、プライマリ2台が落ちた時のみ有効化。ベンチ要員——スタメンが退場した時のみ出場。

Keepalive接続プール:性能倍増の秘密

ここは多くの人が見落とす。Nginxのデフォルト動作:リクエスト毎にバックエンドへのTCP接続を新規作成、レスポンス後に切断。問題ない?大きな問題。

TCP接続は3回ハンドシェイクで確立、4回ハンドシェイクで切断。高コンカレンシーシーン、このオーバーヘッドは恐ろしい。keepalive接続プールで接続を再利用、このコストを削減。

設定例:

upstream backend {
    server 192.168.1.10:8080;
    keepalive 32;  # 各workerが32のidle接続を維持
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

2点注意:

  1. keepalive 32は各workerプロセスが維持する最大idle接続数
  2. proxy_http_version 1.1Connection ""の設定必須——HTTP/1.0は永続接続未対応

APIサービスでテスト——keepaliveなしでQPS約2000、有効化で4000以上。倍増は夸张ではなく、実測値。

2倍
QPS性能向上
来源: 实测数据:启用 keepalive 连接池后

しかしkeepalive値を高く設定すぎない。テスト環境で100に設定、バックエンドが1つのECSコンテナのみ——接続数で圧潰された。本番環境の計算式:

keepalive ≈ 総QPS × 平均レスポンス時間 ÷ workerプロセス数

例:予想QPS 10000、平均レスポンス50ms、4 worker:

10000 × 0.05 ÷ 4 = 125

keepaliveは125程度が合理的。

ヘルスチェック:落ちたら自動除外

Nginx OSS版はパッシブヘルスチェックのみ——リクエスト失敗後にサーバーを unhealthy マーク:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
    }
}

proxy_next_upstreamは次サーバーへリトライする条件定義:接続エラー、タイムアウト、502/503/504。proxy_next_upstream_tries 3は最大3台試行。

しかしパッシブチェックには遅延——リクエスト失敗後にサーバー落ちを発見。可用性が重要なら、NGINX Plusのアクティブヘルスチェックが良い:

upstream backend {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

server {
    location / {
        proxy_pass http://backend;
        health_check interval=5s fails=3 passes=2;
    }
}

5秒毎にアクティブにヘルスチェックリクエスト送信。連続3回失敗で unhealthy マーク、連続2回成功で復帰。


Proxy Buffer:役立つ助手か、邪魔な存在か

バッファリング機構の役割

概念:Nginxはバックエンドからのレスポンスをクライアントに直接送らない——まずバッファに保存。

理由?クライアントのネット速度は予測不能。バックエンドは高速でデータを出力するが、クライアントが低速なら、Nginxは送信完了を待つ必要がある。バッファにより、Nginxはレスポンスを一度保存、ゆっくりクライアントへ送信——バックエンドは待機不要、次リクエストを早期処理可能。

しかしバッファリングには代償:メモリ消費。レスポンスが大きく、コンカレncyが高い場合、メモリ消費は無視できない。

3つの核心パラメータ、関係を理解

proxy_buffer_size 4k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;

この3つは当初混乱した——名前が似て、意味が複雑。図を描いて理解:

  • proxy_buffer_size:レスポンスヘッダー用バッファ、リクエスト1つに1つ
  • proxy_buffers:レスポンスボディ用バッファ配列、形式は 数 各サイズ
  • proxy_busy_buffers_size:クライアント送信中のバッファ部分、総バッファサイズの半分以下

例:proxy_buffers 8 32k、総サイズは8×32k = 256k。proxy_busy_buffers_size 64k、4分の1、ルールに従う。

何时調整?

バックエンドレスポンスヘッダーが巨大(多くのCookieなど)なら、“upstream sent too big header” エラー発生。解決:proxy_buffer_sizeを増大:

proxy_buffer_size 16k;

レスポンスボディが頻繁に大きい(大きなJSONなど)なら、バッファを増大:

proxy_buffers 16 64k;

特殊シーン:バッファを無効化

あるシーンでバッファは問題になる。

Server-Sent Events(SSE):バックエンドがイベントストリームを継続 push。Nginxがバッファすると、クライアントは遅延メッセージを受信。設定でバッファ無効化:

location /events {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 86400s;
}

proxy_read_timeout 86400s(1日)はSSEが長接続、タイムアウト切断不可。

WebSocket:類似、双方向リアルタイム通信:

location /ws {
    proxy_pass http://backend;
    proxy_buffering off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400s;
}

大容量ファイルアップロード:クライアントが1GBアップロード、Nginxが全受信後にバックエンド転送なら、メモリ爆発。リクエストバッファ無効化:

location /upload {
    proxy_pass http://backend;
    proxy_request_buffering off;
    client_max_body_size 1G;
}

proxy_request_buffering offでNginxは直接ストリーム転送——受信と転送を同時実行。


Timeout:設定値の背後にあるロジック

3つのタイムアウトパラメータ、各役割

proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;

名前が似て見える?役割は明確に異なる:

  • proxy_connect_timeout:NginxがTCP接続確立を待つ時間。バックエンドが遅い(ネット拥堵、防火墙)なら、超過で中断。
  • proxy_read_timeout:接続後、Nginxがバックエンドデータを待つ時間。2回読取操作の間隔がこれを超えるとタイムアウト。
  • proxy_send_timeout:Nginxがリクエストボディをバックエンド送信する時間制限。

よくある混乱:proxy_read_timeoutは総タイムアウトではない——読取間隔。バックエンドが5分処理するが、処理中にデータを継続送信(ハートビートなど)なら、proxy_read_timeout 60sで足る。しかしバックエンドが5分完全沈黙なら、proxy_read_timeout 300sが必要。

タイムアウトと502/504の関係

午前3時のアラートで学んだ重要教训:

  • 502 Bad Gateway:Nginxがバックエンドに接続不能——サービスダウン、ポート不通、防火墙遮断
  • 504 Gateway Timeout:Nginxは接続したが、バックエンド処理が長すぎてレスポンスなし

例:proxy_connect_timeout 10s、バックエンドが接続要求に15秒応答なら、Nginxは502返却。しかし接続が迅速確立、バックエンドが2分処理後にデータ返却、proxy_read_timeout 60sなら、504返却。

シーン別タイムアウト戦略

APIサービス:通常30-60秒で足る。APIは迅速レスポンスが期待——短タイムアウトで遅リクエストを発見:

proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;

ファイル処理:レポート出力、PDF生成は数分要する。タイムアウトを緩和:

proxy_connect_timeout 10s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;

ストリーミングサービス:動画、WebSocket、SSE——長接続、1日設定も正常:

proxy_read_timeout 86400s;

502/504トラブルシューティング実践

原因分析

経験したケース:

  1. バックエンドサービス実際ダウン:プロセス崩壊、ポート占有、OOM
  2. 接続数枯渇:バックエンド接続プール満杯、Nginx接続不能
  3. タイムアウト設定過短:午前3時の事例——proxy_read_timeout 60s、バックエンド処理2分要
  4. 防火墙/ネット問題:セキュリティグループルール未設定、iptables遮断

ログ診断方法

最初のステップ:error_log確認:

error_log /var/log/nginx/error.log warn;

よくあるエラー:

upstream timed out (110: Connection timed out) while reading response header from upstream

これは504、読取タイムアウト。

connect() failed (111: Connection refused) while connecting to upstream

これは502、接続拒否——バックエンド未 listen。

高度な手法:upstream状態を表示するカスタムログ形式:

log_format upstream_status '$status $upstream_status $upstream_response_time';

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

200 200, 200, 502 0.5, 1.2, 3.0のような出力——どのバックエンドが何のステータス、どのくらいの時間を返却したか明確表示。

典型的解決策

ケース1:バックエンド処理遅、頻繁504

解決:proxy_read_timeout増大、バックエンドが実際処理完了可能を確認。Nginxのみ調整不可——バックエンドタイムアウトも同期調整。

ケース2:接続拒否、502

解決:バックエンドプロセス稼働、ポート listen、防火墙ルール許可を確認。

netstat -tlnp | grep 8080
ps aux | grep your_app

ケース3:高コンカレncyで接続数枯渇

解決:バックエンド接続プール制限増大、あるいはNginx upstream keepalive有効化で接続作成オーバーヘッド削減。


パフォーマンス最適化ベストプラクティス

Worker設定

Nginxはマルチプロセスモデル。worker_processesでプロセス数設定、通常CPUコア数と等価:

worker_processes auto;

autoはCPUコア数を自動検出。8コアマシン = 8 workerプロセス。

worker_connectionsは各workerが処理可能な最大接続数:

events {
    worker_connections 4096;
}

理論最大コンカレント接続数 = worker_processes × worker_connections。8コア × 4096 = 32768。しかし実際値はシステムファイル descriptor 制限に依存。

TCP最適化3点セット

sendfile on;
tcp_nopush on;
tcp_nodelay on;

この3つ組み合わせで性能を大幅向上:

  • sendfile on:カーネルレベルファイル転送、ユーザー空間バッファを回避
  • tcp_nopush on:sendfileと組み合わせ、パケットを一括送信而非逐次
  • tcp_nodelay on:小データパケット即時送信、バッファ充填を待機しない

静的ファイルサービスでテスト——この3つ有効化でスループット30%以上向上。

30%以上
スループット向上
来源: 实测数据:启用 sendfile + tcp_nopush + tcp_nodelay

他の最適化

gzip圧縮:テキストレスポンス圧縮転送、帯域節約:

gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1024;

ファイル descriptor 制限:高コンカレncyで不足可能。システム制限確認:

ulimit -n

1024のみなら、増大必要。/etc/security/limits.conf編集:

* soft nofile 65535
* hard nofile 65535

完全設定例

本番環境推奨設定テンプレート:

# 基本設定
worker_processes auto;

events {
    worker_connections 4096;
    multi_accept on;
}

http {
    # TCP最適化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Keepalive
    keepalive_timeout 30;
    keepalive_requests 100;

    # バッファ設定
    proxy_buffering on;
    proxy_buffer_size 4k;
    proxy_buffers 8 32k;
    proxy_busy_buffers_size 64k;

    # タイムアウト設定
    proxy_connect_timeout 10s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;

    # gzip
    gzip on;
    gzip_types text/plain text/css application/json;

    upstream backend {
        least_conn;
        server 192.168.1.10:8080 weight=3;
        server 192.168.1.11:8080 weight=2;
        server 192.168.1.12:8080 backup;
        keepalive 32;
    }

    server {
        listen 80;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;

            proxy_next_upstream error timeout http_502 http_503 http_504;
            proxy_next_upstream_tries 3;
        }

        # SSE専用設定
        location /events {
            proxy_pass http://backend;
            proxy_buffering off;
            proxy_read_timeout 86400s;
        }
    }
}

まとめ

多くを語ったが、核心は3点:

  1. upstream設定:負荷分散アルゴリズムを正しく選択、keepalive接続プール有効化、ヘルスチェック設定
  2. バッファ設定:3つのパラメータの関係を理解、特殊シーンでバッファ無効化
  3. タイムアウト設定:各パラメータが何を管理するか理解、シーン別に異なる戦略使用

午前3時の事故で学んだこと:Nginx設定はパラメータを埋めるだけではない。各パラメータの背後に設計ロジック——原理理解で罠を回避。

Nginx初心者なら、デフォルト設定から開始、問題発生時に調整——私みたいに適当に proxy_read_timeout 60s を本番に投入しない。既に罠を踏んだ経験があるなら、この記事で散らばった経験を体系化できるはず。

この記事を書き終えた時、現在の本番環境設定を確認——keepalive 32、proxy_read_timeout 120s、least_conn負荷分散。午前3時のアラートは再び発生していない。


FAQ

proxy_read_timeoutは総タイムアウト?読取間隔?
2回読取操作の間隔。バックエンドが処理中にデータを継続送信(ハートビートなど)なら、総処理時間5分でもproxy_read_timeout 60sで足る。しかしバックエンドが5分完全沈黙なら、300s設定が必要。
proxy_bufferingをいつ無効化する?
3シーンで必須無効化:

• Server-Sent Events(SSE):リアルタイム push、バッファでメッセージ遅延
• WebSocket:双方向リアルタイム通信、ストリーム転送必要
• 大容量ファイルアップロード:メモリ爆発回避、受信と転送を同時実行
keepalive値は何を設定?
計算式:keepalive ≈ 総QPS × 平均レスポンス時間 ÷ worker数。例:QPS 10000、レスポンス50ms、4 workerならkeepalive約125。高すぎ設定不可——100設定でバックエンド圧潰経験あり。
502と504の違いは?
502 Bad GatewayはNginxがバックエンド接続不能(サービスダウン、ポート不通、防火墙遮断)。504 Gateway Timeoutは接続したがレスポンスタイムアウト(バックエンド処理遅)。診断方法は全く異なる:502はプロセスとポート確認、504はタイムアウト設定とバックエンド処理時間確認。
負荷分散アルゴリズム選択は?
シーン別:

• ラウンドロビン(デフォルト):ステートレスサービス、公平分配
• least_conn:長接続シーン(WebSocket、DB接続プール)
• ip_hash:セッション維持必要(一時対応、Redisが良い)
• hash:分散キャッシュ、ヒット率高
upstream sent too big headerの解決?
proxy_buffer_size増大。バックエンドレスポンスヘッダーが巨大(多数Cookieなど)でデフォルト4kバッファ超過。proxy_buffer_size 16k変更で通常解決。
sendfile + tcp_nopush + tcp_nodelayが性能向上する理由?
sendfileはユーザー空間回避で直接カーネル転送、tcp_nopushはパケット一括送信で数減少、tcp_nodelayは小データ即時送信で待機なし。組み合わせで静的ファイルスループット実測30%以上向上。

7 min read · 公開日: 2026年3月30日 · 更新日: 2026年3月30日

コメント

GitHubアカウントでログインしてコメントできます

関連記事