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

Nginx ロードバランシング実践:upstream 設定とヘルスチェック

スマホが激しく震えた。監視ダッシュボードを開くと、backend1 のステータス欄が真っ赤——アプリケーションサーバーが 1 台落ちていた。

あの年のダブルイレブン(独身の日セール)では、バックエンドは 2 台だけ。Nginx の設定には upstream backend { server backend1; server backend2; } と書いてあり、一見対称に見えた。でも backend1 がトラフィックの 70% を受けていた。設定ファイルで先頭に並んでいたからだ。デフォルトのラウンドロビンで、ウェイトもヘルスチェックも未設定だった。

backend1 が落ちた瞬間、ユーザーの注文リクエストはまだその死んだサーバーへ送られ続けた。Nginx は障害を知らず、転送を続ける。ユーザーが見たのは 500 エラーページ。運用担当が手動で backend1 を upstream から外すまで、15 分かかった。

この一件のあと、Nginx upstream はサーバーアドレスを並べるだけではないと学んだ。ウェイト分配、ヘルスチェック、フェイルオーバー——本番で本当に必要なのはここだ。記事では、当時の失敗とその後に身につけた設定方法を整理する。オープンソース Nginx でアクティブヘルスチェックを実装する方法も含む。

1. upstream 基本設定:単一サーバーからクラスタへ

upstream ブロックの役割はシンプルだ。複数サーバーを 1 つの論理グループにまとめ、Nginx に転送先を教える。パラメータは想像以上に豊富。

upstream backend {
  zone backend 64k;
  server backend1.example.com weight=3 max_fails=2 fail_timeout=30s;
  server backend2.example.com;
  server backup1.example.com backup;
}

各行の意味は次のとおり。

zone backend 64k:共有メモリ領域。Nginx の worker プロセス間で、バックエンドの状態(生存 / 障害)を共有する。64k は初期値で、サーバー数が多ければ増やす。これがないと worker ごとに状態がバラバラになり、問題が起きる。

weight=3:ウェイト。backend1 は 3、backend2 はデフォルトの 1。4 リクエスト中 3 件が backend1、1 件が backend2 へ行く。スペックが揃わない環境向け——backend1 が 8 コア 16GB、backend2 が 4 コア 8GB といったケース。

max_fails=2:失敗回数のしきい値。fail_timeout の時間窓内に 2 回失敗すると、Nginx はそのサーバーを unavailable とマークする。デフォルト 1 は敏感すぎる。一瞬のネットワーク揺らぎで発動する。本番では 2 か 3 が無難。

fail_timeout=30s:二重の意味を持つ。第一に、失敗カウントの時間窓が 30 秒。第二に、unavailable になったあと 30 秒経過で再接続を試みる。デフォルト 10 秒は、起動が遅いサービスには短いことがある。

backup:予備サーバー。すべてのプライマリが unavailable のときだけ受け付ける。低スペック機をフェイルセーフに使う用途向け。

down もある。メンテナンス用に手動でオフラインにする:

server backend3.example.com down;  # 一時的にメンテナンスで停止

実務では zone を見落とす人が多い。worker ごとに状態が分かれ、ある worker が障害を検知しても他は転送を続ける。zone を入れると状態同期の問題は解消される。

2. 5 つのロードバランシング戦略:いつ何を使う?

デフォルトの round-robin で足りるかは、状況次第。

デフォルトのまま何年も問題なく回しているプロジェクトも多い。WebSocket の長接続、ショッピングカートの session、キャッシュ穿透(キャッシュミス集中)といった場面では、デフォルトが合わなくなる。選び方の目安を表にまとめた:

シナリオ推奨戦略理由
ステートレス APIround-robin均等分配で十分
WebSocket サービスleast_conn接続数を監視し、過負荷を回避
EC ショッピングカートip_hash同一ユーザーを同一サーバーへ
キャッシュプロキシhash key=$uriキャッシュ失効と穿透を抑制
テスト環境random手早く検証、設定が簡単

round-robin(デフォルト)

何も指定しなければラウンドロビン。リクエストを順番に各サーバーへ送る:

upstream backend {
  server backend1.example.com;
  server backend2.example.com;
  server backend3.example.com;
}

ステートレスサービス向け。各リクエストが独立し、前の状態に依存しない。多くの REST API に適する。

least_conn(最少接続)

現在の接続数が最も少ないサーバーを優先:

upstream websocket_app {
  least_conn;
  server ws1.example.com:8080;
  server ws2.example.com:8080;
}

WebSocket の定番シナリオ。1 ユーザーが長接続を張ると、接続数のばらつきが大きい。ラウンドロビンだと、あるサーバーに長接続が集中したまま新規リクエストも送られ続ける。least_conn は接続数を監視し、負荷の低いサーバーへ振り分ける。

ip_hash(IP ハッシュ)

クライアント IP からハッシュを計算し、同一 IP は常に同一サーバーへ:

upstream shopping_cart {
  ip_hash;
  server cart1.example.com;
  server cart2.example.com;
}

session の一貫性が必要な場面向け。EC カートで cart1 に商品を入れたあと、次のリクエストが cart2 に行くとカートデータが見えない(分散 session ストアを使っていなければ)。ip_hash はこれを防ぐ。

ただし限界もある。サーバーが落ちると、そのサーバーにハッシュされていたユーザーは再分配される。session が失われる可能性がある。session が重要でないケース、または session 共有ストアと組み合わせるのが現実的。

hash(一貫性ハッシュ)

ハッシュキーをカスタム指定でき、一貫性ハッシュにも対応:

upstream cache_proxy {
  hash $uri consistent;
  server cache1.example.com;
  server cache2.example.com;
}

キャッシュプロキシ向け。$uri をキーに consistent を有効にすると、サーバー増減時もキーの一部だけが再マップされ、全体がシャッフルされない。キャッシュヒット率の急落を抑えられる。

random

単純なランダム分配:

upstream test_backend {
  random;
  server test1.example.com;
  server test2.example.com;
}

テスト環境なら十分。本番では制御性が弱いため非推奨。

経験上、多くの Web アプリは round-robin か least_conn で足りる。ip_hash と hash は特定シナリオ向け。「高度に見せたい」だけで無理に使う必要はない。

3. パッシブヘルスチェック:max_fails と fail_timeout

「パッシブ」とは、Nginx が能動的にプローブせず、実際のリクエストの成否で判断する方式。隣人の健康を毎日尋ねるのではなく、散歩や宅配の有無から間接的に推測するイメージだ。

max_fails と fail_timeout が、その「観察メカニズム」の設定になる:

upstream backend {
  server backend1.example.com max_fails=3 fail_timeout=30s;
  server backend2.example.com max_fails=3 fail_timeout=30s;
}

location / {
  proxy_pass http://backend;
  proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
}

proxy_next_upstream は何を「失敗」とみなすかを指定する。error は接続エラー、timeout はタイムアウト、http_500〜504 は HTTP エラー。該当すると次のサーバーへ転送し、現在のサーバーに失敗を 1 回記録する。

30 秒以内に 3 回失敗すると unavailable になる。その後 30 秒間はリクエストを送らない。30 秒後に再接続を試み、成功すれば復帰、失敗ならさらに 30 秒待つ。

弱点は応答の遅さ。実ユーザーが 3 回失敗するまでサーバーは切り離されない。その 3 人はすでにエラーを受け取っている。

極端なケースでは、起動直後のサーバーが不安定なとき、max_fails=1 だと起動中の 1 回の失敗で excluded になり続けることもある。

おすすめの設定:

  • max_fails は 2 か 3。一時的なネットワーク揺らぎを許容
  • fail_timeout は 30 秒以上。復帰の余地を残す
  • proxy_next_upstream に失敗タイプを漏れなく列挙

パッシブの利点は設定が簡単で、オープンソース Nginx がそのまま使えること。欠点はユーザー要求に依存し、体験が先に損なわれること。より早く障害を見つけたいなら、アクティブヘルスチェックへ進む。

4. アクティブヘルスチェック:NGINX Plus とオープンソース代替

アクティブヘルスチェックは、Nginx が定期的にプローブ(例:GET /health)を送り、応答で healthy か判断する。ユーザーが失敗するのを待たず、障害サーバーを事前に切り離せる。

公式は NGINX Plus(商用)。$3,675 / インスタンス / 年。10 インスタンスで年 $36,750。正直、多くの会社には高めに感じる。

オープンソースは nginx_upstream_check_module。淘宝(Taobao)技術チーム開発。Nginx の再コンパイルが必要だが、機能は充実している:

機能NGINX Plusnginx_upstream_check_module
価格$3,675 / 年オープンソース・無料
HTTP チェック対応対応
TCP チェック対応対応
MySQL チェック非対応対応
FastCGI チェック非対応対応
ステータスページ対応対応(check_status)

MySQL と FastCGI チェックは NGINX Plus にない。PHP-FPM や MySQL バックエンドなら、このモジュールが向く。

nginx_upstream_check_module の設定例

upstream backend {
  server backend1.example.com:8080;
  server backend2.example.com:8080;

  check interval=3000 rise=2 fall=5 timeout=1000 type=http;
  check_http_send "GET /health HTTP/1.0\r\n\r\n";
  check_http_expect_alive http_2xx http_3xx;
}

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

  location /upstream_status {
    check_status json;
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    deny all;
  }
}

パラメータの意味

  • interval=3000:3 秒ごとにプローブ
  • rise=2:連続 2 回成功で healthy(起動直後の不安定さを吸収)
  • fall=5:連続 5 回失敗で unavailable(一時的なタイムアウトを許容)
  • timeout=1000:プローブのタイムアウト 1 秒
  • type=http:HTTP プローブ(tcp、ssl_hello、mysql、ajp、fastcgi も可)

check_http_send はプローブの内容。ここでは GET /health。バックエンドは /health を実装し、200 または 3xx を返す必要がある。

check_http_expect_alive は healthy とみなすステータスコード。http_2xx と http_3xx は 200〜299、300〜399 を成功とする。

check_status は監視用ステータスページ。json 出力で Prometheus や Zabbix と連携しやすい。allow / deny はアクセス制御——外部公開は避ける。

モジュールのインストール

nginx_upstream_check_module はコンパイルインストールが必要。おおまかな手順:

# モジュールソースを取得
git clone https://github.com/yaoweibin/nginx_upstream_check_module.git

# Nginx ソースを取得
wget http://nginx.org/download/nginx-1.24.0.tar.gz
tar -zxvf nginx-1.24.0.tar.gz

# パッチ適用(Nginx バージョンに合わせて選択)
cd nginx-1.24.0
patch -p1 < ../nginx_upstream_check_module/check_1.20.1+.patch

# コンパイル
./configure --add-module=../nginx_upstream_check_module
make && make install

Docker ならモジュール入りイメージを自前ビルドするか、コミュニティイメージを探す。

5. 本番環境の実践:セキュリティと監視

本番のヘルスチェック設定でつまずきやすい点を 3 原則にまとめた。

セキュリティ設定:3 つの要点

1. check_status ページは必ずアクセス制御

ステータスページはバックエンド一覧とヘルス状態を公開する。外部から見られると内部トポロジーが漏れ、どのサーバーが落ちているかも分かる——攻撃の好機になる。

location /upstream_status {
  check_status json;
  allow 127.0.0.1;       # ローカル
  allow 10.0.0.0/8;      # 社内 IP
  deny all;              # それ以外は拒否
}

より厳格にするなら、特定の監視サーバー IP のみ許可。

2. 専用ヘルスチェックポートを使う

プローブは高頻度(3〜5 秒に 1 回)。業務ポートを直接叩くと、/health がログに大量記録され、ログ肥大で性能に影響する。

業務ポート(例:8080)とヘルスチェックポート(例:8888)の 2 本立てがおすすめ。ヘルスチェック側はステータスコードだけ返し、業務ロジックもログも最小限に。

check interval=5000 rise=2 fall=3 timeout=2000 type=http port=8888;

port=8888 でプローブ専用ポートを指定。

3. ヘルスチェック API は機密情報を返さない

/health はステータスコードだけで十分。バージョン、設定、メモリ使用量など内部情報は返さない。攻撃者の標的特定に使われる。

# バックエンド実装例
@app.route('/health')
def health():
    return '', 200   # ステータスコードのみ

監視連携:JSON 出力

check_status は複数フォーマットに対応。json は監視システム向け:

curl http://127.0.0.1/upstream_status

出力例:

{
  "servers": {
    "total": 3,
    "generation": 12,
    "server": [
      {"index": 0, "name": "10.0.0.1:8080", "status": "up", "rise": 5, "fall": 0, "type": "http"},
      {"index": 1, "name": "10.0.0.2:8080", "status": "up", "rise": 3, "fall": 0, "type": "http"},
      {"index": 2, "name": "10.0.0.3:8080", "status": "down", "rise": 0, "fall": 5, "type": "http"}
    ]
  }
}

generation は設定変更のカウンター。upstream を変更して reload するたびに増える。監視スクリプトで設定反映を確認できる。

パラメータ調整の目安

interval は 3000ms 未満にしない

プローブが高すぎるとバックエンドに負荷がかかる。3〜5 秒間隔で障害検知は秒単位。ユーザー体験への影響は小さい。

rise と fall のバランス

  • rise が小さすぎる(例:1)と、起動直後の初期化未完了で unhealthy と判定され、すぐ healthy に戻る振動が起きやすい
  • fall が小さすぎる(例:1)と、一瞬のネットワーク揺らぎで切り離され、敏感すぎる

経験値は rise=2、fall=3 か fall=5。瞬間障害は許容し、持続障害を確認してから切り離す。

本番向け完全設定

upstream web_app {
  zone web_app 64k;
  server 10.0.0.1:8080 weight=3;
  server 10.0.0.2:8080;
  server 10.0.0.3:8080 backup;

  check interval=5000 rise=2 fall=3 timeout=2000 type=http port=8888;
  check_http_send "GET /health HTTP/1.1\r\nHost: app.example.com\r\n\r\n";
  check_http_expect_alive http_2xx;
}

server {
  listen 80;
  server_name app.example.com;

  location / {
    proxy_pass http://web_app;
    proxy_set_header Host $host;
    proxy_next_upstream error timeout http_502 http_503 http_504;
  }

  location /upstream_status {
    check_status json;
    allow 127.0.0.1;
    allow 10.0.0.0/8;
    deny all;
  }
}

この設定のポイント:

  • zone で worker 間の状態を同期
  • 5 秒ごとに専用ポートへアクティブプローブ
  • ステータスページは社内のみ
  • proxy_next_upstream で障害サーバー上のリクエストを healthy 側へ転送

まとめ

あのダブルイレブンの事故に戻ると、その後 upstream に zone を入れ、max_fails と fail_timeout を設定し、さらに nginx_upstream_check_module でアクティブヘルスチェックを導入した。サーバーが落ちても Nginx は 5 秒以内に検知して切り離し、ユーザーがエラーを見るケースはほぼなくなった。

ロードバランシング戦略の選び方は一言で言えば、ステートレスなら round-robin か least_conn、ステートフルなら ip_hash か hash。ヘルスチェックは本番必須。オープンソースなら nginx_upstream_check_module。check_status ページのアクセス制御も忘れずに。

まだデフォルトのラウンドロビンでヘルスチェックなしなら、まずパッシブ(max_fails + fail_timeout)から始めるのがよい。変更は小さく、効果はすぐ出る。安定を確認したらアクティブへ進む。テスト環境で試してから本番へ。

FAQ

Nginx upstream の zone 設定は何のために使うの?
zone 設定は共有メモリ領域を作り、複数の worker プロセスがバックエンドの状態情報を共有できるようにします。zone がないと各 worker が独立して状態を管理するため、ある worker が障害を検知しても他の worker がそのサーバーへリクエストを送り続けることがあります。
パッシブヘルスチェックとアクティブヘルスチェックの違いは?
パッシブチェックは実際のリクエストの成否を見てサーバー状態を判断し、ユーザー要求に依存するため障害検知が遅くなります。アクティブチェックは Nginx が定期的にプローブを送るため、ユーザー要求に依存せず、障害を早く見つけて影響前にサーバーを切り離せます。
nginx_upstream_check_module と NGINX Plus、どちらが良い?
それぞれに強みがあります。NGINX Plus は公式サポートと充実したドキュメント、nginx_upstream_check_module はオープンソースで無料、MySQL / FastCGI チェックに対応(NGINX Plus は非対応)です。予算を抑えたいならオープンソースモジュール、安定性と公式サポートを重視するなら NGINX Plus が向いています。
ロードバランシング戦略 round-robin と least_conn はどう選ぶ?
ステートレスな API サービスなら round-robin で均等分配で十分です。WebSocket などの長接続サービスには least_conn が向いており、接続数をリアルタイムに監視して負荷の低いサーバーへ新規リクエストを送れます。一台に長接続が偏るのを防げます。
ヘルスチェックの rise と fall パラメータはどう設定する?
rise は連続成功何回で healthy とマークするかを制御します。2 に設定して、起動直後のサーバーが healthy / unhealthy を繰り返すのを防ぐのがおすすめです。fall は連続失敗何回で unavailable とマークするかで、3 または 5 にして瞬間的な障害を許容します。設定が敏感すぎると誤判定を招きます。
check_status ページにアクセス制御が必要な理由は?
ステータスページはバックエンドのサーバー一覧とヘルス状態を公開します。攻撃者は内部トポロジーを把握でき、どのサーバーが落ちているかも分かるため、攻撃の好機になります。社内 IP や特定の監視サーバーからのみアクセスを許可してください。

5分で読めます · 公開日: 2026年4月27日 · 更新日: 2026年6月8日

関連記事

コメント

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