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、キャッシュ穿透(キャッシュミス集中)といった場面では、デフォルトが合わなくなる。選び方の目安を表にまとめた:
| シナリオ | 推奨戦略 | 理由 |
|---|---|---|
| ステートレス API | round-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 Plus | nginx_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 設定は何のために使うの?
パッシブヘルスチェックとアクティブヘルスチェックの違いは?
nginx_upstream_check_module と NGINX Plus、どちらが良い?
ロードバランシング戦略 round-robin と least_conn はどう選ぶ?
ヘルスチェックの rise と fall パラメータはどう設定する?
check_status ページにアクセス制御が必要な理由は?
5分で読めます · 公開日: 2026年4月27日 · 更新日: 2026年6月8日
Nginx 実践ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
前の記事
Nginx SSL/TLS 設定実践:HTTPS 証明書から A+ セキュリティ強化まで
Nginx で HTTPS をゼロから設定。Let's Encrypt 証明書の取得、TLS 1.3 によるセキュリティ強化、SSL Labs A+ 評価の設定テンプレート、OCSP Stapling のパフォーマンス最適化まで、2026年版の完全ガイド。
第 3 / 6 記事
次の記事
Nginx ダイナミック upstream:Lua でリアルタイムサービスディスカバリを実現
OpenResty ダイナミック upstream の3層アーキテクチャを詳しく解説。ヘルスチェック手法を比較し、Consul/Nacos/etcd 連携コードを提供。コンテナ環境でのリアルタイムサービスディスカバリを実現します。
第 5 / 6 記事
関連記事
Nginx リバースプロキシ完全ガイド:upstream・バッファ・タイムアウト
Nginx リバースプロキシ完全ガイド:upstream・バッファ・タイムアウト
Nginx パフォーマンスチューニング:gzip、キャッシュ、接続プール設定
Nginx パフォーマンスチューニング:gzip、キャッシュ、接続プール設定
Nginx パフォーマンスチューニング実践:gzip、キャッシュ、接続プール設定
コメント
GitHubアカウントでログインしてコメントできます