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

Nginx パフォーマンスチューニング実践:gzip、キャッシュ、接続プール設定

先週、EC サイトのトップページ読み込み時間が 4 秒に急増したというアラートを受け取りました。Chrome DevTools を開いて確認すると、HTML ファイルが 120KB、CSS と JS でさらに 350KB — すべて未圧縮の生ファイルです。さらに悪いことに、すべてのリクエストがバックエンドに到達し、キャッシュヒット率はわずか 12% という悲惨な状態。その夜、残業後に 2 時間かけて Nginx 設定を調整しました。gzip 圧縮を有効化し、キャッシュ戦略を整備し、接続プールパラメータを調整。翌朝確認すると、トップページの読み込みは 1.6 秒まで短縮され、バックエンドの QPS もほぼ半減していました。

正直、このような問題は非常によくあります。多くの人が Nginx をインストールしたままデフォルト設定で運用し、gzip はデフォルトで無効、キャッシュは適当に 2 行書くだけ、接続数上限はデフォルト値のまま。その結果、トラフィックが増えるとサーバーが悲鳴を上げます。

この記事では、本番環境で検証済みの Nginx パフォーマンスチューニング設定をまとめました。gzip 圧縮で転送量を 60-80% 削減、Brotli に切り替えればさらに 15-25% 節約可能。キャッシュヒット率 95% でバックエンド負荷を 90% 削減。接続プール設定を適切に行えば同時接続数は 3-4 倍に向上。さらに Thread Pools や reuseport などの高度な手法を組み合わせれば、単一サーバーで RPS 50K-80K を達成可能です。各モジュールの設定詳細、直面した問題点、実測データを詳しく解説します。

第 1 章:gzip/Brotli 圧縮設定 — 転送量を削減

まず gzip がなぜこれほど重要なのかから説明します。例えば、HTML ファイルの元のサイズが 100KB あるとします。gzip 圧縮を通すと 20-25KB になるかもしれません。これで節約できた 75-80KB の帯域幅は、ユーザーにとってはより速い読み込み速度、運営者にとってはより低いトラフィックコストを意味します。

この問題に初めて気づいたのは、あるクライアントのプロジェクトでした。モバイルユーザーが 60% を超え、多くが 4G 回線でアクセスしていました。トップページの読み込みに 3-4 秒かかり、直帰率は 70% に急増。gzip を有効にした後、転送量は 70% 削減され、初回読み込み時間は約 1.5 秒まで短縮されました。

1.1 gzip 基本設定:まず動かす

Nginx の gzip 設定は実はそれほど複雑ではありません。核となるのは数行です。

http {
    gzip on;
    gzip_vary on;
    gzip_min_length 1000;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}

gzip on はスイッチなので説明不要です。gzip_vary on が重要で、レスポンスヘッダーに Vary: Accept-Encoding を追加します。これにより CDN やブラウザに「このレスポンスの内容はクライアントの圧縮能力によって変わる」ことを伝え、キャッシュの不整合を防ぎます。

gzip_min_length を 1000 バイトに設定すると、1KB 未満のファイルは圧縮しません。小さすぎるファイルは圧縮の恩恵が少なく、逆に CPU を無駄に消費します。gzip_types は圧縮する MIME タイプを指定します。デフォルトでは text/html のみが圧縮されるため、CSS、JS、JSON、XML を追加する必要があります。

1.2 gzip 詳細設定:圧縮レベルと MIME タイプリスト

圧縮レベルはバランスの取れた選択が必要です。Nginx の gzip_comp_level は 1-9 で設定でき、数字が大きいほど圧縮率は高くなりますが、CPU 消費も増加します。

テスト結果は以下の通りです。

圧縮レベルHTML 圧縮率CPU 時間(ms)推奨シナリオ
165%2CPU 負荷が高い環境
472%3バランス型(推奨)
675%5帯域幅が限られる環境(推奨)
978%12極端なケース

正直、レベル 4 と 6 がほとんどの場合で最適な選択です。レベル 9 は CPU 消費が倍増しますが、圧縮率は数ポイントしか向上しません。割に合いません。

以下は本番環境で使用している完全な gzip 設定です。

# gzip 圧縮設定
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml
    application/xml+rss
    application/xhtml+xml
    application/x-javascript;
gzip_disable "msie6";

gzip_proxied any は見落とされがちなパラメータです。Nginx がリバースプロキシとして動作する場合、バックエンドが返すレスポンスヘッダーに Content-Length がないと、デフォルトでは圧縮されません。any に設定すると、条件に合致するすべてのレスポンスを強制的に圧縮します。

gzip_disable "msie6" は古い IE6 との互換性のためです。IE6 は gzip のサポートに問題がありました。現在、IE6 はほぼ絶滅しているため、この行は削除しても問題ありませんが、念のため残しています。

1.3 どのファイルタイプが最も恩恵を受けるか

実測データに基づくと、ファイルタイプによって圧縮効果には大きな差があります。

ファイルタイプ元のサイズ圧縮後圧縮率
HTML100KB20-25KB75-80%
CSS80KB24-28KB65-70%
JavaScript120KB36-42KB65-70%
JSON API50KB20-25KB50-60%
画像/動画既に圧縮済み無意味0-5%

画像と動画はすでに圧縮されている(JPEG、PNG、MP4)ため、さらに gzip しても逆にサイズが増加します。したがって、gzip_typesimage/*video/* を追加してはいけません。追加すると逆効果になります。

ある時、問題の調査を頼まれた際、gzip_typesimage/jpeg が追加されているのを発見しました。結果として画像サイズが逆に 3-5% 増加していました。このような初歩的なミスは、私も以前犯したことがあります — 当時は理解していなかったので、すべての MIME タイプを追加したがっていました。

1.4 Brotli:gzip よりさらに 15-25% 節約

さらに進みたいなら、Brotli はより良い選択肢です。これは Google が開発した圧縮アルゴリズムで、同じ圧縮レベルでも gzip より 15-25% 高い圧縮率を実現します。特に静的リソースに適しており、ブラウザの Brotli サポートはすでに広範に普及しています。

ただし、落とし穴があります。Brotli は Nginx のデフォルトモジュールではなく、追加のコンパイルや動的モジュールのインストールが必要です。私は公式動的モジュールの方法を使用しており、Nginx を再コンパイルする必要がありません。

# まずモジュールをロード(動的モジュールを使用する場合)
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

http {
    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/javascript application/json;
    brotli_min_length 256;
}

Brotli の圧縮レベルも 1-11 ですが、6 を推奨します。レベルが高すぎる(例:11)と圧縮時間が大幅に増加し、動的に生成されるコンテンツには適しません。静的ファイルは事前に Brotli 最高レベルで圧縮し、Nginx が事前圧縮された .br ファイルを直接返すようにできます。

実測比較データは以下の通りです。

圧縮方式100KB HTML 圧縮後圧縮時間ブラウザサポート
gzip (レベル 6)25KB5msほぼすべて
Brotli (レベル 6)18KB15ms95%+
Brotli (事前圧縮 レベル 11)15KB095%+

私の推奨は、動的コンテンツは Brotli レベル 4-6、静的リソースは事前圧縮方式を使用することです。Nginx のコンパイルが面倒なら、gzip でも十分です。75% の圧縮率はすでに十分魅力的です。

第 2 章:キャッシュ戦略設定 — 静的コンテンツの高速化

キャッシュはパフォーマンスチューニングの中で最も直接的な効果をもたらす要素です。適切に設定すれば、95% のリクエストを Nginx から直接返却でき、バックエンドに到達する必要がありません。多くのシステムで、バックエンドサーバーが必死に動いているのに、Nginx 側でキャッシュがほとんど使われていない — 有効化されていないのではなく、設定が間違っているのです。

2.1 proxy_cache と fastcgi_cache の選び方

Nginx は 2 種類のキャッシュメカニズムを提供しています。

  • proxy_cache:上流サーバーのレスポンスをキャッシュ。リバースプロキシシナリオに適用(Node.js、Python、Go サービスなど)
  • fastcgi_cache:FastCGI プロセスのレスポンスをキャッシュ。PHP-FPM シナリオに適用

どちらを選ぶかはバックエンドの技術スタックによります。PHP を使用するなら fastcgi_cache、Node.js、Python、Go なら proxy_cache です。設定ロジックはほぼ同じなので、以下は proxy_cache を例に解説します。

2.2 完全な proxy_cache 設定

まず http ブロックでキャッシュパスを定義します。

http {
    proxy_cache_path /var/cache/nginx
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=10g
                     inactive=60m
                     use_temp_path=off;
}

各行を説明します。

  • levels=1:2:キャッシュディレクトリの階層。1:2 は 2 レベルのディレクトリ構造を意味し、単一ディレクトリのファイル過多を防ぐ
  • keys_zone=my_cache:10m:キャッシュ領域名とメタデータメモリサイズ。10m で約 8 万個のキャッシュキーを保存可能
  • max_size=10g:キャッシュの合計サイズ上限。超過すると LRU アルゴリズムで削除
  • inactive=60m:60 分間アクセスがないキャッシュは削除
  • use_temp_path=off:直接キャッシュディレクトリに書き込み、一時ファイルの移動オーバーヘッドを回避

次に server または location で有効化します。

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        proxy_cache my_cache;

        # キャッシュ有効期間設定
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_valid any 1m;

        # キャッシュキー設計
        proxy_cache_key $scheme$request_method$host$request_uri;

        # フォールバック戦略
        proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

        # レスポンスヘッダーにキャッシュステータスを追加(デバッグ用)
        add_header X-Cache-Status $upstream_cache_status;
    }
}

proxy_cache_valid が核心設定で、ステータスコードごとのキャッシュ期間を定義します。

  • 200 302 10m:正常レスポンスは 10 分間キャッシュ
  • 404 1m:404 エラーは 1 分間キャッシュ。悪意あるリクエストがバックエンドを叩き続けるのを防止
  • any 1m:その他のステータスコードは 1 分間キャッシュ

2.3 キャッシュキー設計と無効化戦略

キャッシュキー proxy_cache_key はどのリクエストが「同一」と見なされるかを決定します。デフォルトは $scheme$proxy_host$request_uri ですが、明示的に宣言することを推奨します。

proxy_cache_key $scheme$request_method$host$request_uri;

これでキャッシュキーにプロトコル、リクエストメソッド、ホスト名、完全な URI が含まれます。GET と POST の両方をサポートするサイトや、複数のドメインがある場合、この設定はより正確です。

キャッシュの無効化は頭の痛い問題です。一般的な戦略は以下の通りです。

  1. 時間切れproxy_cache_valid で時間を設定し、期限切れで自動的に無効化
  2. 能動的なバイパスproxy_cache_bypass でキャッシュを回避
  3. キャッシュパージ:商用版 Nginx Plus には proxy_cache_purge がある

私は一般的に 2 番目の方法を使用し、リクエストヘッダーで制御します。

# 特定のリクエストヘッダーでキャッシュをバイパス
proxy_cache_bypass $http_x_nocache;

# または特定のパラメータでバイパス
proxy_cache_bypass $arg_nocache;

キャッシュを更新したい場合は、?nocache=1 を追加するか、リクエストヘッダー X-Nocache: 1 を送信します。

2.4 フォールバック戦略:バックエンドがダウンしてもサービス継続

proxy_cache_use_stale は非常に実用的な設定です。バックエンドサービスがエラーやタイムアウトした場合、Nginx は期限切れのキャッシュコンテンツを返し、直接エラーを出さずに済みます。

proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

昨年のダブルイレブン(11.11)セール時、バックエンドサービスのスケーリングで問題が発生し、API サービスが断続的に 502 を返していました。幸い、キャッシュにこのフォールバック戦略が設定されていたため、ユーザーへの影響はほぼありませんでした — キャッシュは数分期限切れしていましたが、コンテンツは正常に返却されました。バックエンドが復旧した後、キャッシュは自動的に更新されました。

実測データの比較は以下の通りです。

指標キャッシュなしキャッシュヒットフォールバックモード
レスポンス時間150-200ms5-10ms5-10ms
バックエンド QPS1000500
ユーザー体験正常正常やや遅延

キャッシュヒット時、レスポンス時間は 200ms から 5-10ms に短縮され、約 20 倍高速化されました。この効果は非常に直接的です。

2.5 マイクロキャッシュ:動的コンテンツも高速化

多くの人は動的コンテンツはキャッシュできないと思っていますが、マイクロキャッシュ(microcaching)を使用できます。動的ページを 1-5 秒間キャッシュし、高同時アクセスの瞬間でもバックエンド負荷を大幅に軽減します。

ある EC サイトのトップページでこの手法を試しました。トップページにはリアルタイムレコメンデーションや在庫表示があり、完全に動的化されていました。しかし、セール期間中にトラフィックが急増し、バックエンドが耐えられなくなりました。5 秒間のマイクロキャッシュを追加しました。

proxy_cache_path /var/cache/nginx/micro levels=1:2 keys_zone=micro:10m max_size=1g;

location / {
    proxy_cache micro;
    proxy_cache_valid 200 5s;  # 5 秒間のみキャッシュ
    proxy_cache_lock on;        # キャッシュ更新時の同時リクエストのパッシベーションを防止
    proxy_cache_background_update on;  # バックグラウンドで非同期にキャッシュを更新
}

proxy_cache_lock on が重要です。キャッシュが期限切れした時、最初のリクエストがバックエンドから新しいデータを取得し、他のリクエストは古いキャッシュを使用して待機します。これで「キャッシュが無効になった瞬間に大量のリクエストが同時にバックエンドに到達する」という状況を防げます。

効果は驚異的でした。トップページの TTFB は 800ms から約 5ms に短縮され、バックエンド QPS は 2000 から 400 に低下しました。5 秒のキャッシュ遅延はユーザーがほとんど感知できませんが、サーバー負荷は大幅に軽減されました。

2.6 条件付きリクエスト:帯域幅を節約するテクニック

proxy_cache_revalidate を使用すると、Nginx は条件付きリクエスト(If-Modified-Since / If-None-Match)でバックエンドにキャッシュの有効期限を検証できます。バックエンドが 304 Not Modified を返せば、完全なコンテンツを再送する必要がなく、キャッシュメタデータのみを更新します。

proxy_cache_revalidate on;

この設定は帯域幅に敏感なシナリオで特に有用です。例えば、バックエンドが大きなファイルを返すが、コンテンツの変更頻度が低い場合、条件付きリクエストで大量のデータ転送を節約できます。

第 3 章:接続プール設定 — 高同時接続シナリオの必須知識

gzip とキャッシュが解決するのは「どう転送を高速化するか」で、接続プールが解決するのは「より多くのリクエストをどう処理するか」です。デフォルト設定では、単一の Nginx ワーカーの最大同時接続数は 1024 です。トラフィックが増えると、すぐに不足します。

3.1 worker_connections:上限を正確に計算

最大同時接続数の計算式は以下の通りです。

最大同時接続数 = worker_processes × worker_connections

サーバーが 8 コア CPU で、worker_processes を 8(または auto で自動マッチ)、worker_connections を 4096 に設定すると仮定します。

最大同時接続数 = 8 × 4096 = 32768

この数字は大きく見えますが、各リクエストは通常 2 つの接続を占有することを覚えておく必要があります(クライアントから Nginx、Nginx からバックエンド)。したがって、実際に処理できる同時リクエスト数は、この数字の約半分です。

events ブロックで設定します。

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

use epoll は Linux でデフォルト値なので、明示的に書く必要はありませんが、書くことでより明確になります。multi_accept on でワーカーが複数の新規接続を同時に受け入れ、高同時アクセス時に接続のキューイングを減らします。

3.2 クライアント keepalive:接続を再利用し、オーバーヘッドを削減

TCP 接続の確立には 3 ウェイハンドシェイクが必要で、オーバーヘッドは無視できません。keepalive でクライアントと Nginx 間の接続を再利用し、リクエストごとに再接続する必要をなくします。

http {
    keepalive_timeout 65;
    keepalive_requests 1000;
}

keepalive_timeout 65:接続を 65 秒間維持し、超過したら切断。この値は大きすぎるとサーバーリソースを過度に消費し、小さすぎると再利用効果が不明確です。60-75 秒が合理的な範囲です。

keepalive_requests 1000:単一接続で最大 1000 リクエストを処理。小さすぎると頻繁に切断され、大きすぎるとリソースリークの可能性があります。1000 はテストで最も安定している値です。

新しいパラメータ keepalive_time もあり、単一接続の総生存時間(リクエスト数に関係なく)を制御します。サーバーが長時間稼働している場合、接続の蓄積がリソース問題を引き起こす可能性があるため、追加できます。

keepalive_time 1h;  # 単一接続は最大 1 時間生存

3.3 upstream keepalive:バックエンド接続プール

この設定を知らない人が多いですが、効果は明確です。Nginx とバックエンドサービス間でも接続を再用し、頻繁な TCP 接続確立のオーバーヘッドを削減できます。

upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;

    keepalive 64;
    keepalive_timeout 60s;
    keepalive_requests 1000;
}

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

keepalive 64:64 個のアイドル接続プールを維持。この値はバックエンドサービス数に応じて調整し、通常はバックエンドサーバー数の 4-8 倍に設定します。

proxy_http_version 1.1proxy_set_header Connection "" の 2 行は必須です。HTTP/1.1 はデフォルトで keepalive をサポートし、Connection ヘッダーをクリアして接続を再利用します。この 2 行がないと、upstream keepalive は有効になりません。

実測効果の比較は以下の通りです。

設定接続確立回数/分CPU オーバーヘッド推奨シナリオ
upstream keepalive なし6000低トラフィック
keepalive 323000中トラフィック
keepalive 641500高トラフィック

upstream keepalive を追加した後、接続確立オーバーヘッドは直接 50% 削減されました。この設定は高同時アクセスシナリオで特に重要です。

3.4 ファイルディスクリプタ制限:システムレベルを忘れずに

Nginx の接続数はシステムのファイルディスクリプタ上限に制限されます。worker_connections を 4096 に設定しても、システムが各プロセスで 1024 ファイルしか開けないなら、やはり不足します。

現在の制限を確認します。

ulimit -n

戻り値が 65536 未満の場合、システム設定で増やす必要があります。/etc/security/limits.conf を編集します。

* soft nofile 65536
* hard nofile 65536

次に Nginx 設定でも宣言します。

worker_rlimit_nofile 65536;

この設定は main ブロック(worker_processes と同レベル)に配置し、Nginx 起動時に十分なファイルディスクリプタを申請させます。

3.5 推奨パラメータ早見表

異なるシナリオでの推奨設定をまとめました。

パラメータ低トラフィック(<1000 QPS)中トラフィック(1000-5000 QPS)高トラフィック(>5000 QPS)
worker_processesautoautoauto
worker_connections102420484096
keepalive_timeout606575
keepalive_requests1005001000
upstream keepalive163264
worker_rlimit_nofile4096819265536

これは出発点にすぎず、実際の調整には負荷テストデータが必要です。私は通常 wrk や ab で負荷テストを行い、接続数とレスポンス時間の変化曲線を見て、最適値を見つけます。

第 4 章:高度な最適化 — Thread Pools と reuseport

最初の 3 章でほとんどのシナリオの最適化をカバーしました。トラフィックが特に多い場合(単一サーバーで RPS 50K 以上を目指す)、または遅延に特に敏感な場合、さらに 2 つの高度な設定を試せます。

4.1 Thread Pools:sendfile のボトルネックを突破

Nginx はデフォルトでシングルスレッドのイベント駆動モデルで、ほとんどのシナリオで十分効率的です。しかし、高同時静的ファイルサービスでは、隠れたボトルネックがあります。sendfile はゼロコピーですが、ファイル読み込みとソケット書き込みが同じワーカースレッドで行われます。ディスク I/O が遅いと、ワーカー全体がブロックされます。

Thread Pools はこの問題を解決します。ファイル読み込みと送信を独立したスレッドプールに分割し、ワーカーはスケジューリングのみを担当し、I/O でブロックされません。

Nginx 公式ブログにテストケースがあります。単一サーバーで 9 倍のパフォーマンス向上。テストシナリオは 1MB ファイルのダウンロードで、元々はディスク I/O の制限を受けていましたが、Thread Pools 追加後にボトルネックを突破しました。

設定方法は以下の通りです。

http {
    thread_pool default threads=32 max_queue=65536;
    aio threads=default;
    sendfile_max_chunk 512k;
}

threads=32 はスレッドプールに 32 スレッド、max_queue=65536 は最大キュータスク数を意味します。aio threads=default で非同期 I/O を有効化し、どのスレッドプールを使用するかを指定します。sendfile_max_chunk 512k はデータブロックごとの送信サイズを制御し、大きなファイルが長時間占有するのを防ぎます。

ただし、Thread Pools は万能薬ではありません。バックエンドが主に動的コンテンツ(API サービス)の場合、ディスク I/O はボトルネックではなく、追加すると逆にコンテキストスイッチのオーバーヘッドが増加します。私の推奨は、静的ファイルサービスや大きなファイルのダウンロードシナリオでのみ検討することです。

4.2 Socket Sharding (reuseport):接続遅延を低減

Nginx 1.9.1 で reuseport パラメータが導入され、複数のワーカーが独立して同じポートをリッスンできるようになり、ワーカー間でのロック競合を回避できます。

従来のモードでは、すべてのワーカーが 1 つのリッスンソケットを共有し、新規接続時に accept mutex を奪い合います。高同時アクセス時に顕著なロック競合オーバーヘッドと遅延ジッターが発生します。

reuseport を追加した後:

server {
    listen 80 reuseport;
}

各ワーカーが独立したリッスンソケットを持ち、カーネルが自動的に新規接続を異なるワーカーに分配します。ロック競合が完全に排除されます。

実測データ(Nginx 公式ブログより):

指標reuseport なしreuseport あり
平均遅延15.65ms12.35ms
遅延標準偏差3.5ms1.2ms
接続分布不均一均一

遅延が 21% 削減され、より重要なのは遅延ジッターが大幅に減少したことです。この設定は高同時アクセスシナリオ(QPS 20K 超過)で最も効果的で、低トラフィックシナリオでは体感できないかもしれません。

4.3 open_file_cache:ファイルディスクリプタキャッシュ

静的ファイルサービスシナリオでは、open_file_cache でファイルディスクリプタとメタデータをキャッシュし、リクエストごとにディスクを参照するのを防げます。

http {
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}
  • max=10000:最大 10000 ファイル情報をキャッシュ
  • inactive=30s:30 秒間アクセスがないと削除
  • open_file_cache_valid 60s:60 秒ごとにキャッシュの有効期限を検証
  • open_file_cache_errors on:ファイルが存在しないなどのエラーステータスもキャッシュ

この設定は静的サイトで明確な効果があります。ただし、動的コンテンツシナリオでは推奨しません。ファイルが頻繁に変更される可能性があり、キャッシュが原因でコンテンツが更新されなくなるからです。

第 5 章:統合設定テンプレート — 本番環境ですぐに使える

最初の 4 章で原理を説明したので、ここでは統合後の設定テンプレートを直接提示します。実際のシナリオに応じてパラメータを調整できますが、フレームワークは普遍的です。

# nginx.conf 本番環境テンプレート(高トラフィックシナリオ)

user nginx;
worker_processes auto;
worker_rlimit_nofile 65536;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # gzip 圧縮設定
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1000;
    gzip_types text/plain text/css text/xml text/javascript
               application/json application/javascript application/xml
               application/xml+rss application/xhtml+xml;
    gzip_disable "msie6";

    # キャッシュパス設定
    proxy_cache_path /var/cache/nginx
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=10g
                     inactive=60m
                     use_temp_path=off;

    # クライアント接続設定
    keepalive_timeout 65;
    keepalive_requests 1000;
    keepalive_time 1h;

    # ファイルキャッシュ(静的サイトでオプション)
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;

    # バックエンドサーバーグループ
    upstream backend {
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
        keepalive 64;
        keepalive_timeout 60s;
        keepalive_requests 1000;
    }

    server {
        listen 80 reuseport;
        server_name example.com;

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

            # キャッシュ設定
            proxy_cache my_cache;
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            proxy_cache_key $scheme$request_method$host$request_uri;
            proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
            proxy_cache_revalidate on;

            # デバッグ用レスポンスヘッダー
            add_header X-Cache-Status $upstream_cache_status;
        }
    }
}

3 つのシナリオタイプの差別化設定

EC サイト:トップページと商品詳細ページの変更頻度が高く、キャッシュ時間は短めで 10-15 分で十分。upstream keepalive は大きめに設定。データベースクエリが多く、バックエンド負荷が高いため。セール期間中はマイクロキャッシュを有効化し、1-3 秒間キャッシュ。

API サービス:データのリアルタイム性要求が高く、proxy_cache_valid は 1-5 分のみ。gzip は JSON に効果的で必ず追加。Brotli もインストール可能なら追加。API レスポンスは小さいがリクエスト頻度が高く、圧縮効果は大きい。

静的サイト:HTML、CSS、JS はほぼ変更されないため、キャッシュは 1 時間以上に設定可能。gzip 圧縮の効果が最大で、静的ファイルはテキストが多い。open_file_cache と Thread Pools を有効化し、ファイルディスクリプタキャッシュと非同期 I/O は静的コンテンツに明確な効果がある。

パフォーマンス向上比較(更新版)

設定項目最適化前最適化後向上率
HTML 転送量 (gzip)100KB25KB75% ↓
HTML 転送量 (Brotli)100KB18KB82% ↓
API レスポンス時間(キャッシュヒット)200ms8ms96% ↓
同時接続能力100040004x ↑
RPS (単一サーバー最適化後)10K50K-80K5-8x ↑
TTFB (reuseport)15.65ms12.35ms21% ↓

これらのデータは実測と公式ドキュメントを総合したものです。実際の効果はサーバー構成、ネットワーク環境、ビジネス特性に依存するため、負荷テストでの検証が重要です。

第 6 章:よくある問題とトラブルシューティング

私が頻繁に遭遇した問題と、その解決策をまとめました。

Q:gzip 圧縮が効かない。レスポンスヘッダーに Content-Encoding: gzip がない

3 箇所を確認してください:

  1. gzip on が正しい設定レベルにあるか(http ブロック)
  2. gzip_types にレスポンスの MIME タイプが含まれているか
  3. レスポンスサイズが gzip_min_length 以上か

curl でテスト:curl -H "Accept-Encoding: gzip" -I http://your-site.com

Q:キャッシュヒット率が低い。X-Cache-Status がほとんど MISS

一般的な原因:

  • キャッシュキーの設計が不適切で、各リクエストが「異なる」と見なされている
  • proxy_cache_valid の設定が短すぎて、キャッシュが使わないうちに期限切れ
  • バックエンドのレスポンスヘッダーに Cache-Control: no-cacheSet-Cookie がある

レスポンスヘッダーを確認し、キャッシュを禁止するディレクティブがないか確認してください。

Q:worker_connections が不足し、502 エラーが発生

Nginx のエラーログを確認し、worker_connections are not enough があれば、同時接続数が上限を超えています。

解決策:

  1. worker_connections の値を増やす
  2. 接続リークがないか確認(keepalive 設定が不合理)
  3. サーバーを追加してロードバランシングを検討

Q:gzip と sendfile は競合する?

競合しません。gzip はレスポンス内容を圧縮し、sendfile はファイル転送方式を処理します。両方を同時に有効化できます。唯一の注意点は、gzip は動的コンテンツをメモリ内で圧縮するため sendfile を使用しないこと。静的な事前圧縮ファイルは直接 sendfile できます。

Q:upstream keepalive が効かない?

2 つの一般的な原因:

  1. proxy_http_version 1.1 が抜けている。HTTP/1.0 はデフォルトで keepalive 非対応
  2. proxy_set_header Connection "" が抜けている。このヘッダーをクリアしないと接続が閉じられる

この 2 行は必ず location ブロックに追加し、upstream ブロックではありません。

Q:reuseport で “duplicate listen options” エラー?

reuseport は listen 行で 1 回のみ宣言可能で、他の場所で繰り返せません。各 server ブロックの listen 行に reuseport が最大 1 回しか出現しないことを確認してください。複数の server が同じポートをリッスンする場合、それぞれに独立して reuseport を追加します。

Q:メモリ使用量が高く、サーバーが頻繁に OOM

可能性のある原因:

  • proxy_cache_pathkeys_zone が大きすぎる
  • キャッシュファイルが多すぎて、メモリマップの使用量が高い
  • keepalive 接続プールが大きすぎて、アイドル接続がリソースを占有

これらのパラメータを適切に減らすか、サーバーにメモリを追加してください。


まとめ

これまで説明してきましたが、Nginx パフォーマンスチューニングの核心は 3 つのこと:圧縮、キャッシュ、接続プールです。さらにいくつかの高度な手法(Brotli、マイクロキャッシュ、Thread Pools、reuseport)を組み合わせれば、単一サーバーの RPS を 10K から 50K-80K に引き上げられます。

チューニングは一度行えば終わりではありません。以下のロードマップを推奨します:

  1. まず gzip を有効化:変更が最小で、効果が最も直接的。10 分で完了
  2. 次にキャッシュを設定:ビジネスシナリオに応じてキャッシュ戦略を設計。1 日以内に完了可能
  3. その後、接続プールを調整:負荷テストでの検証が必要。トラフィックが安定してから実施
  4. 最後に高度な最適化を試す:Thread Pools と reuseport。トラフィックが特に多い場合のみ検討

各変更後、忘れずに負荷テストで効果を検証してください。wrk や ab が使用でき、レスポンス時間、QPS、エラー率の変化を確認します。感覚で調整せず、データで判断してください。

最後のチェックリストを参考にしてください:

  • gzip が有効で、MIME タイプが完全に設定されている
  • gzip_comp_level が 4-6 に設定され、CPU と圧縮率のバランスが取れている
  • Brotli が設定されている(利用可能な場合)
  • proxy_cache_path が設定され、キャッシュサイズが適切
  • proxy_cache_valid がビジネスシナリオに応じて設定されている
  • マイクロキャッシュが有効(高同時動的コンテンツシナリオ)
  • proxy_cache_use_stale フォールバック戦略が設定されている
  • worker_connections が 4096 以上に設定されている
  • keepalive_timeout が 60-75 秒に設定されている
  • upstream keepalive が設定されている(HTTP/1.1 と Connection ヘッダーを含む)
  • reuseport が有効(高トラフィックシナリオ)
  • ファイルディスクリプタ制限が増やされている(65536)
  • レスポンスヘッダーにデバッグ用 X-Cache-Status が含まれている

だいたいこれで完了です。質問があればコメント欄に投稿してください。できる限り返信します。

FAQ

gzip 圧縮が効かない。レスポンスヘッダーに Content-Encoding: gzip がない場合は?
3 箇所を確認してください:1) gzip on が http ブロックにあるか、2) gzip_types にレスポンスの MIME タイプが含まれているか、3) レスポンスサイズが gzip_min_length 以上か。curl -H "Accept-Encoding: gzip" -I で検証できます。
キャッシュヒット率が低い。X-Cache-Status がほとんど MISS の場合の調査方法は?
一般的な原因:キャッシュキーの設計が不適切、proxy_cache_valid の設定が短すぎる、バックエンドが Cache-Control: no-cache や Set-Cookie を返している。レスポンスヘッダーを確認し、キャッシュを禁止するディレクティブがないか確認してください。
worker_connections が不足し、502 エラーが発生する場合の解決策は?
Nginx のエラーログを確認し、"worker_connections are not enough" がある場合は同時接続数が上限を超えています。解決策:worker_connections の値を増やす、接続リークがないか確認、サーバーを追加してロードバランシングを行う。
upstream keepalive が効かない一般的な原因は?
2 つの一般的なミス:1) proxy_http_version 1.1 が抜けている(HTTP/1.0 はデフォルトで keepalive 非対応)、2) proxy_set_header Connection "" が抜けている。この 2 行は必ず location ブロックに追加してください。
Brotli と gzip はどう使い分けるべき?
Brotli は gzip より 15-25% 圧縮率が高いですが、追加モジュールのインストールが必要です。動的コンテンツは Brotli レベル 4-6、静的リソースは事前圧縮でレベル 11 を使用。Nginx のコンパイルが面倒なら、gzip レベル 6 でも十分で、75% の圧縮率を達成できます。
異なるトラフィックシナリオの推奨設定パラメータは?
低トラフィック(<1000 QPS):worker_connections 1024、keepalive 16。中トラフィック(1000-5000 QPS):2048、keepalive 32。高トラフィック(>5000 QPS):4096、keepalive 64。実際は負荷テストデータに基づいて調整が必要です。

10 min read · 公開日: 2026年5月15日 · 更新日: 2026年5月15日

シリーズの読書導線 第 6 / 6 記事

Nginx 実践ガイド

検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。

シリーズ全体を見る

関連記事

コメント

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