はじめに
Webアプリを速くするには「どこで」キャッシュするかが鍵です。nginxには大きく2種類のキャッシュ戦略があります。クライアント側に保存するブラウザキャッシュと、nginxサーバ自身が保存するプロキシキャッシュ(サーバキャッシュ)です。それぞれの仕組み・設定方法・使い分けを解説します。
キャッシュの基本フロー
リクエストがどこでキャッシュされるかを確認しましょう。
① ブラウザキャッシュHIT(最速)
ブラウザ → ローカルキャッシュ (ネットワーク不使用)
② サーバキャッシュHIT
ブラウザ → nginx → proxy_cache (オリジン不使用)
③ キャッシュMISS(フルリクエスト)
ブラウザ → nginx → キャッシュなし → オリジンサーバ
ブラウザキャッシュ
ブラウザキャッシュは、nginxがHTTPレスポンスヘッダに指示を書き込み、ブラウザに「このファイルはN秒間ローカルに保存してよい」と伝える仕組みです。次回アクセス時にネットワークを一切使わずに表示できるため、体感速度の改善に直結します。
主要ディレクティブ:expires と Cache-Control
# 静的アセット向け設定例
server {
location ~* \.(js|css|png|jpg|svg|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache";
}
}
immutable ディレクティブは「有効期限内はリロードしても再検証しない」という指示です。ファイル名にハッシュを含む場合(例:main.a3f9b.js)と組み合わせると最も効果的です。
Cache-Control の主な値
キャッシュを許可する値
- public — CDNも含めキャッシュ可
- max-age=N — N秒間キャッシュ
- immutable — 期限内は再検証不要
- stale-while-revalidate — 古いキャッシュを返しつつ更新
キャッシュを制限する値
- no-cache — 毎回サーバに再検証
- no-store — 一切保存しない
- private — ブラウザのみ可(CDN不可)
- must-revalidate — 期限切れなら必ず検証
ETag / Last-Modified による条件付きリクエスト
no-cache を指定した場合、ブラウザは毎回サーバに確認します。このとき ETag や If-Modified-Since ヘッダを使った条件付きリクエストが発生し、変更がなければ 304 Not Modified が返るため転送量を節約できます。
# nginxデフォルトでETagは有効。無効化したい場合: etag off; # Last-Modifiedも合わせて制御する場合: add_header Last-Modified ""; if_modified_since off;
注意:APIレスポンスや個人情報を含むページには必ず Cache-Control: no-store, private を設定してください。誤って public にするとCDNや共有プロキシに機密情報が残るリスクがあります。
サーバキャッシュ(proxy_cache)
nginxをリバースプロキシとして使う場合、バックエンドのレスポンスをnginx自身がディスクにキャッシュできます。バックエンドへのリクエスト数を劇的に減らせるため、サーバ負荷軽減とスループット向上に効果的です。
基本設定
# http ブロックでキャッシュゾーンを定義
http {
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
}
# server / location ブロックで有効化
server {
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating;
proxy_cache_lock on;
add_header X-Cache-Status $upstream_cache_status;
}
}
キャッシュステータスを確認する
X-Cache-Status ヘッダにより、DevToolsで各リクエストのキャッシュ状態を確認できます。
- HIT — キャッシュから返却
- MISS — バックエンドにリクエスト
- STALE — 期限切れキャッシュを使用
- BYPASS — キャッシュをスキップ
キャッシュのバイパスとパージ
# Cookieやクエリパラメータによってキャッシュをスキップ
proxy_cache_bypass $cookie_session $arg_nocache;
proxy_no_cache $cookie_session $arg_nocache;
# PURGEメソッドでキャッシュを即時削除(ngx_cache_purge モジュール)
location ~ /purge(/.*) {
allow 127.0.0.1;
deny all;
proxy_cache_purge my_cache $scheme$proxy_host$1;
}
proxy_cache_lock on を設定すると、同じURLへの同時リクエストが来た際にバックエンドへ流れるのを1リクエストだけに絞れます(Thundering Herd 問題の防止)。
2つのキャッシュの違い
ブラウザキャッシュとサーバキャッシュの主な違いを表にまとめます。
| 項目 | ブラウザキャッシュ | サーバキャッシュ |
|---|---|---|
| 保存場所 | ユーザのブラウザ | nginxサーバのディスク |
| 主な用途 | 静的ファイル(JS/CSS/画像) | 動的コンテンツ、APIレスポンス |
| 制御方法 | Cache-Control / Expires ヘッダ | proxy_cache_* ディレクティブ |
| 即時無効化 | 難しい(URLを変える必要がある) | PURGEリクエストで即時削除可 |
| 効果の対象 | 個々のユーザの体感速度 | 全ユーザへのバックエンド負荷 |
実践的な組み合わせパターン
最も効果的なのは両方を組み合わせることです。静的ファイルはブラウザキャッシュ、動的コンテンツはサーバキャッシュでバックエンドを保護しましょう。
# 推奨パターン:ハッシュ付き静的ファイル
location ~* \.(js|css)$ {
expires 1y;
add_header Cache-Control "public, immutable";
gzip_static on;
}
# HTMLはno-cache(常に最新のハッシュURLを持つhtmlを取得)
location ~* \.html$ {
add_header Cache-Control "no-cache";
proxy_cache my_cache;
proxy_cache_valid 200 5m;
}
# 共通APIはサーバキャッシュ
location /api/public/ {
proxy_cache my_cache;
proxy_cache_valid 200 1m;
add_header Cache-Control "no-store";
}
# プライベートAPIはキャッシュなし
location /api/private/ {
proxy_cache_bypass 1;
add_header Cache-Control "no-store, private";
}
デプロイ時のポイント:静的ファイルにはビルド時にコンテンツハッシュを付与(webpack や Vite のデフォルト動作)することで、Cache-Control: immutable, max-age=31536000 を安全に使えます。ユーザは自動的に新しいURLにアクセスするため、キャッシュの強制削除が不要になります。


