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;
}
各行を解説します:
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 パフォーマンスチューニングの核心は 3 本柱: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 の設定が必須な理由は?
ビジネスシナリオ別のキャッシュ時間の設定は?
8 min read · 公開日: 2026年4月11日 · 更新日: 2026年4月11日
関連記事
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
GitHub Actions 入門:YAML ワークフローの基礎とトリガー設定
n8n 実践ガイド:Webhook トリガーと IF/Switch 条件分岐の設計

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