2026年6月18日
2026年6月18日
Nginxのproxy_cache設定|キャッシュの基本設定・除外条件・削除・HIT確認の方法
Nginx の proxy_cache は、リバースプロキシとして受けたバックエンド(PHP-FPM の前段の Apache、アプリケーションサーバーなど)の応答をディスクに保存し、同じ URL への次のリクエストにはバックエンドへ問い合わせずに応答する機能です。設定は次の 2 段階です。
httpブロックで、キャッシュの置き場所と大きさをproxy_cache_pathで定義するlocationブロックで、proxy_cacheを使ってそのキャッシュを有効にし、何をどれだけの時間保存するかを決める
この記事の設定は、Nginx 1.30.6(nginx.org の公式 Docker イメージ)と Debian 13 の Nginx 1.26.3 で nginx -t と動作を確認しました(2026年11月時点)。ディレクティブの意味は公式ドキュメント(ngx_http_proxy_module)に基づいています。
設定の全体像
先に完成形を示します。/etc/nginx/nginx.conf の http ブロックか、そこから読み込まれるファイル(公式パッケージなら /etc/nginx/conf.d/*.conf、Debian/Ubuntu のパッケージなら /etc/nginx/sites-enabled/ 配下も)に書きます。
# http ブロックに書く
proxy_cache_path /var/cache/nginx/proxy
levels=1:2
keys_zone=MAIN_CACHE:10m
max_size=1g
inactive=60m
use_temp_path=off;
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache MAIN_CACHE;
proxy_cache_key $scheme$host$request_uri;
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
add_header X-Cache-Status $upstream_cache_status always;
}
}
listen 443 ssl http2; という書き方は Nginx 1.25.1 で非推奨になり、http2 on; を別に書く形に変わりました。古いバージョンでは http2 on; は使えないので、nginx -v でバージョンを確かめてください。
書き終えたら、構文を確認してから再読み込みします。
sudo nginx -t
sudo systemctl reload nginx
proxy_cache_path の各項目
proxy_cache_path は http ブロックにだけ書けます。
| 項目 | 意味 |
|---|---|
/var/cache/nginx/proxy |
キャッシュファイルを置くディレクトリ。Nginx のワーカープロセスのユーザーが書き込める必要がある |
levels=1:2 |
サブディレクトリの階層。ファイルが 1 か所に集中しないようにする |
keys_zone=MAIN_CACHE:10m |
キャッシュのキーとメタ情報を置く共有メモリの名前と大きさ。公式ドキュメントでは 1MB でおよそ 8,000 キー |
max_size=1g |
ディスク上のキャッシュの上限。超えると、使われていないものから cache manager プロセスが削除する |
inactive=60m |
この時間アクセスされなかったキャッシュは、有効期限内でも削除される(既定 10 分) |
use_temp_path=off |
バックエンドからの応答を一時ディレクトリを経由せず、キャッシュのディレクトリに直接書く |
max_size を書かないとキャッシュはディスクの空きがある限り増えます。ディスクの容量に合わせて上限を決めておきます。
location 側の主なディレクティブ
| ディレクティブ | 意味 |
|---|---|
proxy_cache MAIN_CACHE |
keys_zone で付けた名前のキャッシュを使う |
proxy_cache_key |
キャッシュを区別するキー。既定は $scheme$proxy_host$request_uri |
proxy_cache_valid 200 301 302 10m |
ステータスコードごとの保存時間。時間だけを書くと 200・301・302 だけが保存される |
add_header X-Cache-Status $upstream_cache_status always |
キャッシュを使ったかどうかを応答ヘッダーに出す(確認用) |
既定のキー $proxy_host は proxy_pass に書いた接続先の名前です。1 台のバックエンドに複数のドメインを振り分けている場合、ドメインが違っても同じキーになるため、例では $host(リクエストのドメイン)を使っています。
既定でキャッシュされないもの
公式ドキュメントのとおり、次の応答は設定を足さなくても保存されません。
GETとHEAD以外のリクエスト(proxy_cache_methodsの既定がGET HEAD)。POST を除外する設定を別に書く必要はありません- バックエンドの応答に
Set-Cookieヘッダーが含まれるもの Cache-Controlにno-cache・no-store・privateが含まれるもの、Expiresが過去の日時のものVary: *が付いたもの
確認環境でも、POST には X-Cache-Status が付かず、Set-Cookie を返す URL は何度アクセスしても MISS のままでした。
バックエンドの Cache-Control を無視する場合
アプリケーションが常に Cache-Control: no-cache を返していてキャッシュされないときは、proxy_ignore_headers でそのヘッダーを無視できます。
proxy_ignore_headers Cache-Control Expires;
これを入れると、決済画面やマイページなどバックエンドが意図して no-store を返しているページも保存の対象になります。次の節の除外条件と組み合わせ、個人ごとに内容が変わるページが保存されないことを確かめてから使ってください。Set-Cookie まで無視する設定(proxy_ignore_headers Set-Cookie)は、他人のセッション Cookie を配ってしまうおそれがあるため避けます。
ログイン中のユーザーや管理画面を除外する
除外の条件は、if を並べるより map で変数を作るほうが読みやすく、http ブロックにまとめて書けます。次は WordPress の例です。
# http ブロックに書く
map $http_cookie $skip_cache_cookie {
default 0;
~*wordpress_logged_in_ 1;
~*wp-postpass_ 1;
~*comment_author_ 1;
}
map $request_uri $skip_cache_uri {
default 0;
~^/wp-admin/ 1;
~^/wp-login\.php 1;
~^/wp-json/ 1;
}
# location / の中に書く
proxy_cache_bypass $skip_cache_cookie $skip_cache_uri;
proxy_no_cache $skip_cache_cookie $skip_cache_uri;
proxy_cache_bypass:値のどれかが空でも0でもなければ、キャッシュを読まずにバックエンドへ問い合わせるproxy_no_cache:同じ条件で、バックエンドの応答をキャッシュに保存しない
2 つを同じ条件で並べるのが基本です。bypass だけだと、ログイン中に取得したページがキャッシュに保存され、ログインしていない人に表示されることがあります。
WordPress 以外のアプリケーションでは、ログインを示す Cookie 名と管理画面のパスをそのアプリケーションに合わせて書き換えます。WordPress での設定をさらに詳しく知りたい場合は WordPressをNginxのproxy_cacheで高速化する方法 も参照してください。
キャッシュが効いているかを確かめる
同じ URL に 2 回アクセスし、X-Cache-Status を見ます。
curl -s -o /dev/null -D - https://example.com/sample-page/ | grep -i x-cache-status
curl -s -o /dev/null -D - https://example.com/sample-page/ | grep -i x-cache-status
確認環境では 1 回目が MISS、2 回目が HIT になり、ログイン Cookie を付けたリクエストと /wp-admin/ 配下は BYPASS になりました。
| 値 | 意味 |
|---|---|
MISS |
キャッシュに無く、バックエンドから取得した |
HIT |
キャッシュから応答した |
BYPASS |
proxy_cache_bypass の条件に当たり、キャッシュを使わなかった |
EXPIRED |
期限切れだったため、バックエンドから取り直した |
STALE |
期限切れのキャッシュをそのまま返した(proxy_cache_use_stale による) |
UPDATING |
更新中のため、期限切れのキャッシュを返した |
REVALIDATED |
proxy_cache_revalidate on で、バックエンドに更新の有無を問い合わせて再利用した |
何度アクセスしても MISS のままのときは、応答ヘッダーに Set-Cookie や Cache-Control: no-cache などが付いていないかを確認します。
curl -s -o /dev/null -D - https://example.com/sample-page/
add_header は、同じブロックに add_header が 1 つでもあると上位のブロックの add_header を引き継がない仕様です。server ブロックにセキュリティヘッダーを書いている場合、location に X-Cache-Status を足すとそれらが消えるので、必要なヘッダーは location 側にも書き直します。
アクセスログでヒット率を見る
$upstream_cache_status をログに出すと、どれだけキャッシュから応答できているかを集計できます。log_format は http ブロックに書きます。
log_format cache '$remote_addr [$time_local] "$request" $status '
'cache=$upstream_cache_status rt=$request_time';
access_log /var/log/nginx/cache.log cache;
grep -o 'cache=[A-Z-]*' /var/log/nginx/cache.log | sort | uniq -c
キャッシュを通らなかったリクエスト(静的ファイルを Nginx が直接返した場合など)は cache=- になります。
キャッシュを削除する
すべて削除する
キャッシュのディレクトリの中身を消します。Nginx を止める必要はありません。
sudo find /var/cache/nginx/proxy -type f -delete
キャッシュファイルには拡張子が付かないので、-name "*.cache" のような条件では見つかりません。
1 件だけ削除する
キャッシュファイルの名前は、キャッシュキーの MD5 値です。levels=1:2 の場合、MD5 値の末尾 1 文字と、その手前の 2 文字がディレクトリ名になります。キーが $scheme$host$request_uri なら、https://example.com/sample-page/ のキーは httpsexample.com/sample-page/ です。
KEY='httpsexample.com/sample-page/'
HASH=$(printf '%s' "$KEY" | md5sum | cut -d' ' -f1)
FILE="/var/cache/nginx/proxy/${HASH: -1}/${HASH: -3:2}/$HASH"
sudo grep -a '^KEY:' "$FILE" # キーが一致するか確認
sudo rm -f "$FILE"
このスクリプトは bash 用です(${HASH: -1} は bash の書き方)。キャッシュファイルの先頭付近には KEY: の行があるので、キーの組み立てを間違えていないかをそこで確かめられます。確認環境では削除後の次のアクセスが MISS になり、新しく保存されました。
キーがわからないときは、ファイルの中身から探すこともできます。
sudo grep -rla 'KEY: httpsexample.com/sample-page/' /var/cache/nginx/proxy
URL で削除する(ngx_cache_purge モジュール)
proxy_cache_purge はオープンソース版の Nginx に標準では含まれていません。サードパーティーの ngx_cache_purge モジュールが必要です。Debian 13 では libnginx-mod-http-cache-purge パッケージで入ります。
sudo apt install libnginx-mod-http-cache-purge
# server ブロックに書く(location / より前に)
location ~ ^/purge(/.*)$ {
allow 127.0.0.1;
deny all;
proxy_cache_purge MAIN_CACHE $scheme$host$1;
}
2 番目の引数は、proxy_cache_key と同じ形のキーにします。proxy_pass の無い location では $proxy_host が空になるため、キーに $proxy_host を使っていると削除の対象が一致しません。
curl -s http://127.0.0.1/purge/sample-page/ -H 'Host: example.com'
確認環境(Debian 13・Nginx 1.26.3)では Successful purge と表示され、次のアクセスが MISS になりました。同じ URL を続けて削除するとエラーのステータスが返ります。商用版の NGINX Plus にも同じ名前のディレクティブがありますが、書き方が異なります。
バックエンドの障害時と同時アクセスへの備え
# location / の中に書く
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
| ディレクティブ | 意味 |
|---|---|
proxy_cache_use_stale |
バックエンドがエラーやタイムアウトを返したとき、期限切れのキャッシュを返す。updating は更新中にも古いキャッシュを返す指定 |
proxy_cache_background_update on |
期限切れのキャッシュを返しつつ、裏で取り直す。proxy_cache_use_stale に updating が無いと働かない |
proxy_cache_lock on |
同じキーのキャッシュが無いとき、バックエンドへ問い合わせるのを 1 リクエストに絞り、ほかは保存を待つ |
proxy_cache_lock_timeout 5s |
待つ上限。過ぎたリクエストはバックエンドに直接問い合わせる(その応答は保存されない) |
アクセスが集中した瞬間にキャッシュが切れて、多数のリクエストが同時にバックエンドへ流れる状況を和らげる設定です。
注意点
- キャッシュは URL 単位です。ログイン状態・言語・端末によって内容が変わるページは、除外するかキーに条件を加えないと、別の人向けの内容が表示されます。
- 記事を更新しても、
proxy_cache_validの時間が過ぎるまでは古い内容が返ります。更新が多いサイトは時間を短くするか、公開時に削除する運用を決めておきます。 - キャッシュのディレクトリはディスクを消費します。
max_sizeを設定したうえで、du -sh /var/cache/nginx/proxyで使用量を確認します。
リバースプロキシそのものの設定は Nginxでリバースプロキシを設定する方法、map の書き方は Nginxのmapモジュールの使い方 で説明しています。