Cache-Control
使い方
オリジン、中間装置、クライアントはいずれも、保存されたレスポンスがいつまで使えるのか、いつ新しいコピーが必要なのかを一致させるために Cache-Control に頼ります。1 つのレスポンスで複数のディレクティブを組み合わせ、完全なキャッシュポリシーを表現することがよくあります。どれだけの期間保存するのか、誰が保存してよいのか、鮮度が切れた後に何をするのか、といった内容です。
以下の表は、どのディレクティブがリクエスト、レスポンス、あるいはその両方に適用されるかを示します。
複数のディレクティブは、1 つのヘッダー値の中でカンマで区切るか、複数の Cache-Control ヘッダーに分けて記述します。矛盾するディレクティブが現れた場合は、最も制限的な組み合わせが適用されます。
注意
認識されないディレクティブはキャッシュに無視されます。これにより、古い実装を壊すことなく新しいディレクティブを導入できます。
注意
Cache-Control ヘッダーがない場合、キャッシュはヒューリスティックな鮮度を適用します。よく使われるヒューリスティックは、Last-Modified からの経過時間の 10% です。つまり、Cache-Control のないレスポンスはブラウザや CDN にキャッシュされます。Cache-Control を明示的に設定することで、想定外のキャッシュ動作を防げます。
注意
Cache-Control はいつ再検証するか(鮮度の有効期間)を統制します。ETag はどのように再検証するか(コンテンツの同一性)を統制します。両者は連携して機能します。Cache-Control は再検証が必要かどうかを決め、ETag はコンテンツが変化したかどうかを決めます。Pragma ヘッダーは Cache-Control: no-cache の HTTP/1.0 における同等物であり、事実上非推奨です。
max-age
max-age ディレクティブは、レスポンスが指定された秒数より古くない場合にのみ、保存されたレスポンスを返すようキャッシュに指示します。負の数のような不正な値は 0 として扱われます。
Cache-Control: max-age=<seconds>
max-age=0 を設定すると、エンドツーエンドの再検証が強制されます。このパターンは、no-cache をサポートしない HTTP/1.0 の実装に由来します。
max-stale
max-stale ディレクティブは、Age が鮮度の有効期間を指定された秒数まで超過したレスポンスを受け入れることを示します。許容のウィンドウは max-age が切れた時点から始まります。
Cache-Control: max-stale=<seconds>
このディレクティブは、オリジンサーバーに一時的に到達できず、やや古いレスポンスでも受け入れられる場合に役立ちます。
min-fresh
min-fresh ディレクティブは、残りの鮮度の有効期間が少なくとも指定された秒数あるレスポンスを要求します。キャッシュは、レスポンスがその追加期間にわたって新鮮なままである場合にのみ、保存したコピーを返します。
Cache-Control: min-fresh=<seconds>
no-cache
no-cache を伴うリクエストは、コピーを配信する前に保存されたレスポンスをオリジンで検証することをキャッシュに要求します。これにより、保存されたエントリを破棄することなく再検証が強制されます。
Cache-Control: no-cache
注意
キャッシュにレスポンスをまったく保存させないようにするには、代わりに no-store を使ってください。
no-store
no-store ディレクティブは、リクエストや対応するレスポンスを保存しないようキャッシュに求めます。
Cache-Control: no-store
no-transform
no-transform ディレクティブは、画像の再圧縮やメディア形式の変換といったレスポンスボディの改変を中間装置に禁じます。この制約は、中間装置がキャッシュプロキシであっても転送ゲートウェイであっても適用されます。
Cache-Control: no-transform
only-if-cached
only-if-cached ディレクティブは、オリジンに接続せずに保存されたレスポンスを返すようキャッシュに指示します。適切な保存済みレスポンスがない場合、キャッシュは 504 のステータスを返します。
Cache-Control: only-if-cached
max-age
max-age ディレクティブは、生成後にレスポンスが新鮮なままである秒数を宣言します。タイマーはオリジンがレスポンスを作成した瞬間から始まるため、転送時間や中間キャッシュで費やされた時間もこの予算に算入されます。
Cache-Control: max-age=<seconds>
max-age=3600 のレスポンスは 1 時間新鮮なままです。1 時間が経過すると、キャッシュはそのレスポンスを古いものとして扱い、他に存在するディレクティブに応じて再検証するか新しいコピーを取得します。
s-maxage
s-maxage(共有 max-age)ディレクティブは、CDN やプロキシサーバーのような共有キャッシュにおいて max-age を上書きします。プライベートなブラウザキャッシュは s-maxage を無視し、max-age にフォールバックします。
Cache-Control: s-maxage=<seconds>
no-cache
レスポンスの no-cache ディレクティブは、再利用のたびに保存されたレスポンスをオリジンで再検証することをキャッシュに要求します。キャッシュはレスポンスを保存し続けるため、ETag や Last-Modified による条件付きリクエストが可能です。
Cache-Control: no-cache
no-cache がフィールド名のリストを伴う場合、それらの特定のヘッダーだけが再検証を必要とします。保存されたレスポンスの残りの部分は、オリジンに接続せずに配信されます。
Cache-Control: no-cache="Set-Cookie"
注意
保存を完全に防ぐには no-store を使ってください。
no-store
no-store ディレクティブは、いかなるキャッシュもそのレスポンスを保存しないようにします。以降のリクエストはすべてオリジンへ向かいます。
Cache-Control: no-store
注意
no-cache は保存を許可しますが、使用のたびに再検証を要求します。キャッシュは If-None-Match や If-Modified-Since でオリジンに確認し、コンテンツが変わっていなければ 304 を受け取ります。no-store は保存を完全に防ぐため、コピーは一切保持されず、すべてのリクエストがレスポンス全体を取得します。よくある誤りは、銀行や医療の記録のような機微なデータに no-store を意図しながら no-cache を使ってしまうことです。no-cache は頻繁に更新されるコンテンツに効率的です。no-store は、決してディスクに残してはならないコンテンツのためのものです。
no-transform
レスポンスの no-transform ディレクティブは、中間装置がコンテンツをキャッシュするかどうかに関わらず、転送前にレスポンスボディを改変することを防ぎます。
Cache-Control: no-transform
must-revalidate
must-revalidate ディレクティブは、新鮮な間はレスポンスを配信することをキャッシュに許可します。古くなると、キャッシュは再びレスポンスを配信する前にオリジンに接続して再検証します。オリジンに到達できない場合、キャッシュは古いコンテンツを配信せずに 504 を返します。
Cache-Control: must-revalidate
proxy-revalidate
proxy-revalidate ディレクティブは must-revalidate と同じように機能しますが、その要件が共有キャッシュにのみ適用される点が異なります。プライベートなブラウザキャッシュは影響を受けません。
Cache-Control: proxy-revalidate
must-understand
must-understand ディレクティブは、キャッシュがステータスコードを認識し、それに関連するキャッシュの要件を理解している場合にのみレスポンスを保存するよう指示します。
Cache-Control: must-understand, no-store
must-understand を no-store と組み合わせることでフォールバックが得られます。must-understand に対応していないキャッシュは未知のディレクティブを無視し、代わりに no-store に従います。
private
private ディレクティブは、保存をプライベートなキャッシュ、通常はエンドユーザーのブラウザに限定します。CDN やプロキシサーバーのような共有キャッシュはそのレスポンスを破棄します。
Cache-Control: private
public
public ディレクティブは、レスポンスが共有キャッシュに保存される資格があることを示します。Authorization ヘッダーを伴うレスポンスは、public ディレクティブが存在しない限り共有キャッシュに保存されません。
Cache-Control: public
注意
must-revalidate や s-maxage がすでに存在する場合、public ディレクティブは不要です。
immutable
immutable ディレクティブは、鮮度の有効期間中にレスポンスボディが変化しないことを保証します。キャッシュは immutable なリソースに対する条件付きリクエストを省略するため、バージョン付きの JavaScript バンドルやフィンガープリント付きの画像といったアセットで再検証の往復がなくなります。
Cache-Control: public, max-age=31536000, immutable
stale-while-revalidate
stale-while-revalidate ディレクティブは、古くなったレスポンスを使える期間を延長します。鮮度が切れた後、キャッシュはバックグラウンドで再検証しながら古いコピーを配信します。パラメーターは、古いレスポンスが受け入れ可能であり続ける追加の秒数を定義します。
Cache-Control: max-age=600, stale-while-revalidate=30
バックグラウンドの再検証が完了すると、キャッシュは古いエントリを新しいレスポンスで置き換えます。このパターンは、再検証の遅延をエンドユーザーから隠します。
stale-if-error
stale-if-error ディレクティブは、オリジンがエラーのステータス(500、502、503、504)を返すか到達できない場合に、古いレスポンスを配信することをキャッシュに許可します。パラメーターは、鮮度の有効期間を超えて古いレスポンスをフォールバックとして使える秒数を設定します。
Cache-Control: max-age=600, stale-if-error=86400
例
長い鮮度の有効期間と immutable のフラグを付けて配信される静的アセットです。ブラウザとあらゆる共有キャッシュは、このレスポンスを再検証なしで 1 年間保存します。
Cache-Control: public, max-age=31536000, immutable
要求元のブラウザのみを対象とした API のレスポンスです。キャッシュはレスポンスを 5 分間保存し、古くなった後は再利用の前に再検証します。
Cache-Control: private, max-age=300, must-revalidate
共有キャッシュには 10 分のウィンドウを、ブラウザキャッシュには 1 分を与える CDN 向けのポリシーです。CDN がバックグラウンドで再検証する間、古いレスポンスが最大 30 秒間配信されます。
Cache-Control: s-maxage=600, max-age=60, stale-while-revalidate=30
Googlebot と max-age
Google のクロール基盤はヒューリスティックな HTTP キャッシュを実装しています。max-age ディレクティブは、Googlebot が再クロールの頻度を判断するのに役立ちます。max-age が長いページは再取得の頻度が下がり、no-cache はコンテンツが頻繁に変わることを示すためより頻繁なクロールが妥当とみなされます。キャッシュのヘッダーを一切持たないレスポンスは、特定のステータスコードについて既定でヒューリスティックにキャッシュ可能なので、キャッシュを完全に防ぎたいオリジンは明示的に no-store を送る必要があります。Bingbot はリクエストに Cache-Control: no-cache を送り、クロールのペース調整には robots.txt の crawl-delay と IndexNow に依拠します。
注意
SEO とキャッシュに関する支援については、元 Google の SEO コンサルタント Search Brothers にご相談ください。
トラブルシューティング
想定外のキャッシュ動作は、ディレクティブの誤解、CDN による上書き、矛盾するヘッダーに起因します。
no-cache ディレクティブがあるのに古いコンテンツが配信される。no-cache ディレクティブは保存を防ぎません。キャッシュはレスポンスを保存し、再利用のたびに再検証します。保存を完全に防ぐには no-store を使ってください。機微なデータが関わる場合は両方を組み合わせます。Cache-Control: no-cache, no-store のようにします。
CDN が Cache-Control ディレクティブを無視する。多くの CDN は、オリジンの Cache-Control を自身のポリシーで上書きします。Cloudflare は既定でオリジンのヘッダーを尊重しますが、Page Rule や Cache Rule が有効な場合はそれらを上書きします。AWS CloudFront は、カスタム TTL を伴うキャッシュポリシーが割り当てられていない限りオリジンのヘッダーを使います。オリジンの設定をデバッグする前に、CDN のダッシュボードで上書きの規則を確認してください。
max-age を下げた後も古いコンテンツが残る。CDN のエッジノードやブラウザのキャッシュは、元の max-age が切れるまでレスポンスを保持します。オリジンで値を下げても、すでにキャッシュされたコピーは消去されません。キャッシュの有効期間を変更した後は CDN のパージを実施してください。Cloudflare では URL 単位でパージするか、ゾーン全体のパージを使います。CloudFront では該当するパスに対して無効化を作成します。
no-store があるのに戻る・進むの操作でブラウザが古いページを表示する。Chrome、Firefox、Safari の back/forward cache(bfcache)は、no-store を迂回してメモリからページの状態全体を復元します。この挙動は仕様どおりであり、キャッシュの違反ではありません。ヘッダーがブラウザに届いていることを確認するには、DevTools の Network タブで「Disable cache」のチェックを外した状態で Cache-Control ヘッダーを調べてください。unload のイベントリスナーを追加すると一部のブラウザで bfcache が無効になりますが、性能は低下します。
Expires ヘッダーが Cache-Control と矛盾する。両方のヘッダーが存在する場合、Cache-Control: max-age が Expires に優先します。混乱を避けるため Expires ヘッダーは削除してください。nginx では次のようにします。
expires off;
add_header Cache-Control "max-age=3600";
Apache では次のようにします。
Header unset Expires
Header set Cache-Control "max-age=3600"
curl と DevTools でキャッシュのヘッダーを診断する。curl -I https://example.re を実行して、オリジンからのレスポンスヘッダーを調べます。-H “Cache-Control: no-cache” を加えると、CDN のキャッシュを迂回してオリジンに直接到達できます。ブラウザの DevTools では Network タブを開いてリクエストを選択し、Response Headers のセクションを確認します。Age、X-Cache、CF-Cache-Status のヘッダーを見て、そのレスポンスが CDN のエッジノードから来たのかオリジンから来たのかを判断してください。