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% 削減でき、キャッシュヒット率が 95% ならバックエンドの負荷を 90% 減らせ、接続プールを適切に設定すれば同時接続能力を 3-4 倍にできます。各モジュールの設定の詳細、踏んできた落とし穴、実測データをすべて余すところなく説明します。
第 1 章:gzip 圧縮設定 — 転送量を削減する
まず gzip がなぜこれほど重要なのかを説明します。想像してみてください。あなたの HTML ファイルの元サイズが 100KB で、gzip で圧縮するとわずか 20-25KB になるかもしれません。この節約できた 75-80KB の帯域幅は、ユーザーにとっては読み込み速度の向上、あなたにとってはトラフィックコストの低減を意味します。
私が初めてこの問題を意識したのは、あるクライアントのプロジェクトでした。彼らのモバイルユーザー比率は 60% を超えており、多くが 4G ネットワーク環境でアクセスしていました。トップページの読み込みに 3-4 秒かかり、直帰率は 70% まで跳ね上がっていました。gzip を追加したところ、転送量は一気に 70% 削減され、初回表示の読み込み時間は 1.5 秒前後まで下がりました。
1.1 基本設定:まず動かす
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 発展設定:圧縮レベルと MIME タイプ一覧
圧縮レベルはバランスが必要な選択です。Nginx の gzip_comp_level は 1-9 に設定でき、数字が大きいほど圧縮率は高くなりますが、CPU 消費も増えます。
私は次のようなテストを行いました:
| 圧縮レベル | HTML 圧縮率 | CPU 時間(ms) | 推奨シナリオ |
|---|---|---|---|
| 1 | 65% | 2 | CPU が逼迫 |
| 4 | 72% | 3 | バランス型(推奨) |
| 6 | 75% | 5 | 帯域幅が逼迫(推奨) |
| 9 | 78% | 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 どのファイルタイプが最も効果的か?
私が実測したデータによると、ファイルタイプごとの圧縮効果の差はかなり大きいです:
| ファイルタイプ | 元サイズ | 圧縮後 | 圧縮率 |
|---|---|---|---|
| HTML | 100KB | 20-25KB | 75-80% |
| CSS | 80KB | 24-28KB | 65-70% |
| JavaScript | 120KB | 36-42KB | 65-70% |
| JSON API | 50KB | 20-25KB | 50-60% |
| 画像/動画 | 圧縮済み | 意味なし | 0-5% |
画像や動画はそれ自体がすでに圧縮されており(JPEG、PNG、MP4)、さらに gzip をかけるとかえって体積が増えます。ですから gzip_types には絶対に image/* や video/* を加えないでください。加えると逆効果です。
あるとき他の人の問題を調査していて、彼らの gzip_types に image/jpeg が加えられているのを発見しました。その結果、画像の体積が逆に 3-5% 増えていたのです。こうした初歩的なミスは、私もおそらく犯したことがあります——わからなかった頃は、すべての MIME タイプを加えたくて仕方なかったのです。
第 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;
}
1 行ずつ説明します:
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 の両方をサポートしている場合や、複数のドメインがある場合、この設定はより正確になります。
キャッシュの失効は頭の痛い問題です。よくある戦略は次のとおりです:
- 時間による失効:
proxy_cache_validで時間を設定し、期限切れで自動的に失効する - 能動的なクリア:
proxy_cache_bypassでキャッシュをバイパスする - キャッシュの削除:商用版 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;
昨年の独身の日(ダブルイレブン)、バックエンドサービスのスケールアウト時に少し問題が起き、API サービスが断続的に 502 を返しました。幸いキャッシュにこの降格戦略を設定していたため、ユーザーのアクセスはほとんど影響を受けませんでした——キャッシュは数分間期限切れでしたが、内容は正常に返せたのです。バックエンドが復旧すると、キャッシュは自動的に更新されました。
実測データの比較:
| 指標 | キャッシュなし | キャッシュヒット | 降格モード |
|---|---|---|---|
| レスポンス時間 | 150-200ms | 5-10ms | 5-10ms |
| バックエンド QPS | 1000 | 50 | 0 |
| ユーザー体験 | 正常 | 正常 | やや遅い |
キャッシュヒット時、レスポンス時間は 200ms から 5-10ms まで下がり、20 倍近く速くなりました。この効果はあまりにも直感的です。
第 3 章:接続プール設定 — 高並行シナリオの必須項目
gzip とキャッシュが解決するのは「どうすればより速く転送できるか」で、接続プールが解決するのは「どうすればより多くのリクエストを処理できるか」です。デフォルト設定では、単一の Nginx worker の最大同時接続数はわずか 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 は worker が同時に複数の新規接続を受け入れられるようにし、高並行シナリオで接続のキュー待ちを減らせます。
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 は私がテストした結果、比較的安定した値です。
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.1 と proxy_set_header Connection "" の 2 行は必ず追加してください。HTTP/1.1 はデフォルトで keepalive をサポートし、Connection ヘッダーを空にして初めて接続を再利用できます。この 2 行を書かないと、upstream keepalive は効きません。
実測効果の比較:
| 設定 | 接続確立回数/分 | CPU オーバーヘッド | 推奨シナリオ |
|---|---|---|---|
| upstream keepalive なし | 6000 | 高 | 低トラフィック |
| keepalive 32 | 3000 | 中 | 中トラフィック |
| keepalive 64 | 1500 | 低 | 高トラフィック |
upstream keepalive を追加すると、接続確立のオーバーヘッドが一気に 50% 削減されました。この設定は高並行シナリオでとりわけ重要です。
3.4 推奨パラメータ早見表
異なるシナリオでの推奨設定をまとめました:
| パラメータ | 低トラフィック(<1000 QPS) | 中トラフィック(1000-5000 QPS) | 高トラフィック(>5000 QPS) |
|---|---|---|---|
| worker_processes | auto | auto | auto |
| worker_connections | 1024 | 2048 | 4096 |
| keepalive_timeout | 60 | 65 | 75 |
| keepalive_requests | 100 | 500 | 1000 |
| upstream keepalive | 16 | 32 | 64 |
これはあくまで出発点で、実際の調整は負荷テストのデータと組み合わせる必要があります。私は通常 wrk や ab で負荷テストを行い、接続数とレスポンス時間の変化曲線を見て、最適値を探します。
第 4 章:総合設定テンプレート — 本番環境ですぐ使える
前 3 章で原理を説明しましたが、ここでは統合した設定テンプレートを直接示します。実際のシナリオに応じてパラメータを調整できますが、フレームワークは汎用的です。
# nginx.conf 本番環境テンプレート
user nginx;
worker_processes auto;
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;
# バックエンドサーバーグループ
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;
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;
# デバッグ用レスポンスヘッダー
add_header X-Cache-Status $upstream_cache_status;
}
}
}
3 つのシナリオの使い分け設定
EC サイト:トップページと商品詳細ページは変化が頻繁なので、キャッシュ時間は短めに、10-15 分で十分です。データベースクエリが多くバックエンドの負荷が高いため、upstream keepalive は大きめにします。
API サービス:データのリアルタイム性が求められるため、proxy_cache_valid は 1-5 分程度になることがあります。gzip は JSON に効果が高いので、必ず追加します。
静的サイト:HTML、CSS、JS はほとんど変わらないので、キャッシュは 1 時間あるいはそれ以上に設定できます。静的ファイルはテキストが多いため、gzip 圧縮の効果が最大になります。
第 5 章:よくある問題とトラブルシューティング
私が遭遇したことのある頻出問題をいくつか、解決策とともに直接示します。
Q:gzip 圧縮が効かず、レスポンスヘッダーに Content-Encoding: gzip がない
3 か所を確認します:
gzip onが正しい設定階層(http ブロック)にあるかgzip_typesにレスポンスの MIME タイプが含まれているか- レスポンス体積が
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-cacheやSet-Cookieがある
レスポンスヘッダーを確認し、キャッシュを禁止する指定がないことを確かめます。
Q:worker_connections が足りず、502 エラーが発生する
Nginx のエラーログを見て、worker_connections are not enough があれば、同時接続数が上限を超えています。
解決策:
worker_connectionsの値を増やす- 接続リークがないか確認する(keepalive 設定が不適切)
- サーバー台数を増やして負荷分散を検討する
Q:メモリ使用量が高すぎて、サーバーが頻繁に OOM になる
考えられる原因:
proxy_cache_pathのkeys_zoneが大きすぎる- キャッシュファイルが多すぎて、メモリマッピングの占有が高い
keepaliveの接続プールが大きすぎて、アイドル接続がリソースを占有している
これらのパラメータを適度に小さくするか、サーバーにメモリを追加します。
最後に
Nginx パフォーマンスチューニングの核心となる三本柱は、gzip 圧縮、キャッシュ戦略、接続プール設定です。3 つのモジュールがうまくかみ合えば、サイトの読み込み速度を倍にし、同時接続能力を 3-4 倍に高められます。
チューニングは一度やったら終わりというものではありません。私はこのルートをお勧めします:
- まず gzip を有効化:変更が最小で効果が最も直接的、10 分で完了
- 次にキャッシュを設定:業務シナリオに応じてキャッシュ戦略を設計、1 日以内に完了できる
- 最後に接続プールを調整:負荷テストでの検証が必要なので、トラフィックが安定した後に行うのが適切
変更のたびに、必ず負荷テストで効果を検証してください。wrk でも ab でも構いません。レスポンス時間、QPS、エラー率の変化を見ます。感覚で調整せず、データで語りましょう。
最後にチェックリストです。照らし合わせて実行してください:
- gzip を有効化し、MIME タイプの設定が完全
- gzip_comp_level を 4-6 に設定し、CPU と圧縮率のバランスを取る
- proxy_cache_path を設定し、キャッシュサイズが妥当
- proxy_cache_valid を業務シナリオに応じて設定
- proxy_cache_use_stale の降格戦略を設定済み
- worker_connections を 4096 以上に設定
- keepalive_timeout を 60-75 秒に設定
- upstream keepalive を設定済み(HTTP/1.1 と Connection ヘッダーを含む)
- レスポンスヘッダーにデバッグ用の X-Cache-Status を含める
おおよそこんなところです。質問があればコメント欄に書いてください。できる限り返信します。
Nginx パフォーマンスチューニング設定の流れ
gzip 圧縮、キャッシュ戦略、接続プール最適化を 3 ステップで本番環境向けに設定
⏱️ 目安時間: 30 分
- 1
ステップ1: gzip 圧縮を有効化する
http ブロックに以下の設定を追加します:
• gzip on; 圧縮を有効化
• gzip_vary on; Vary レスポンスヘッダーを追加
• gzip_comp_level 6; 圧縮レベルを設定(推奨 4-6)
• gzip_min_length 1000; 1KB 未満は圧縮しない
• gzip_types で MIME タイプを指定:text/plain text/css application/json application/javascript - 2
ステップ2: proxy_cache キャッシュを設定する
2 ステップで設定します:
ステップ 1:キャッシュパスを定義
• proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m
ステップ 2:location でキャッシュを有効化
• proxy_cache my_cache;
• proxy_cache_valid 200 10m; 正常なレスポンスを 10 分キャッシュ
• proxy_cache_use_stale error timeout http_502; 降格戦略を設定 - 3
ステップ3: 接続プールのパラメータを最適化する
3 つの重要な設定:
• worker_connections 4096; events ブロックに設定
• keepalive_timeout 65; keepalive_requests 1000; http ブロックに設定
• upstream keepalive 64; proxy_http_version 1.1; proxy_set_header Connection ""; バックエンド接続プールの設定
FAQ
gzip の圧縮レベルはどのくらいが適切ですか?
proxy_cache と fastcgi_cache はどう選びますか?
worker_connections はどのくらいに設定すべきですか?
キャッシュヒット率が低いときはどう調査しますか?
upstream keepalive はなぜ HTTP/1.1 が必須なのですか?
業務シナリオごとのキャッシュ時間はどう設定しますか?
6分で読めます · 公開日: 2026年4月11日 · 更新日: 2026年6月8日
Nginx 実践ガイド
検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。
前の記事
Nginx リバースプロキシ完全ガイド:upstream・バッファ・タイムアウト
Nginx リバースプロキシの3大コア設定を徹底解説。upstream 負荷分散、proxy buffer のバッファ調整、timeout の設定を扱い、502/504 エラーの調査、keepalive 接続プール、ヘルスチェックなど実践的なテクニックを網羅します。
第 1 / 6 記事
次の記事
Nginx SSL/TLS 設定実践:HTTPS 証明書から A+ セキュリティ強化まで
Nginx で HTTPS をゼロから設定。Let's Encrypt 証明書の取得、TLS 1.3 によるセキュリティ強化、SSL Labs A+ 評価の設定テンプレート、OCSP Stapling のパフォーマンス最適化まで、2026年版の完全ガイド。
第 3 / 6 記事
関連記事
Nginx ロードバランシング実践:upstream 設定とヘルスチェック
Nginx ロードバランシング実践:upstream 設定とヘルスチェック
Nginx ダイナミック upstream:Lua でリアルタイムサービスディスカバリを実現
Nginx ダイナミック upstream:Lua でリアルタイムサービスディスカバリを実現
Nginx パフォーマンスチューニング実践:gzip、キャッシュ、接続プール設定
コメント
GitHubアカウントでログインしてコメントできます