HTTPQUERY
日本語

Vary

使い方

1 つの URL でも、クライアントが何を送るかによって異なるレスポンスを生むことがよくあります。あるクライアントには gzip で、別のクライアントには Brotli で圧縮するサーバーは、同じリソースについて 2 つの異なる表現を配信していることになります。Vary ヘッダーは、どのリクエストヘッダーがサーバーに特定の表現を選ばせたのかを記録し、適切なコピーを保存して照合するために必要な情報をキャッシュに与えます。

ある URL について保存済みのレスポンスを持つキャッシュがリクエストを受け取ると、キャッシュは新しいリクエストと保存済みのリクエストの間で、列挙された Vary のヘッダーを比較します。値が一致すれば、キャッシュ済みのレスポンスは有効です。異なれば、キャッシュはオリジンから新しいレスポンスを取得します。

最も一般的な値は Accept-Encoding(圧縮のネゴシエーション用)と Accept(コンテンツネゴシエーション用)です。User-Agent の文字列に基づいてモバイルとデスクトップのクライアントに異なる HTML を配信するダイナミックサービングの構成では、この差異を示すために Vary: User-Agent を加えます。

ワイルドカードの値 * は、すべてのリクエストが一意とみなされることを意味し、実質的にそのリソースのキャッシュを無効にします。これが適切であることはまれで、ほとんどのキャッシュは Vary: * のレスポンスをキャッシュ不可として扱います。

注意

Google は、検索のインデックス処理においてモバイルとデスクトップの関係を把握するために Vary ヘッダーを使いません。このヘッダーはキャッシュの仕組みであり、検索のシグナルではありません。Vary: User-Agent を伴うダイナミックサービングを使うサイトは、正しいキャッシュ動作という恩恵は受けられますが、このヘッダー自体が Google によるモバイルコンテンツの発見やインデックスに影響することはありません。また Google は、モバイルファーストインデックスであるにも関わらず m-dot の URL を正規版にすることは避けるよう助言しており、推奨される構成としてレスポンシブデザインを挙げています。

Accept-Encoding

レスポンスが Accept-Encoding リクエストヘッダーによって変わることを示します。異なる圧縮アルゴリズム(gzip、br、zstd)は、同じリソースについて異なるレスポンスボディを生みます。これは最も広く使われる Vary の値です。

Accept

レスポンスが Accept リクエストヘッダーによって変わることを示します。コンテンツネゴシエーションは、クライアントの好みに基づいて同じ URL から異なるメディアタイプ(HTML、JSON、XML)を配信します。

Accept-Language

レスポンスが Accept-Language リクエストヘッダーによって変わることを示します。同じ URL からローカライズされたコンテンツを配信する多言語サイトは、この値を含めます。

User-Agent

レスポンスが User-Agent リクエストヘッダーによって変わることを示します。ダイナミックサービングの構成は、同じ URL からモバイルとデスクトップのクライアントに異なる HTML を配信します。

Vary: Cookie は、レスポンスが Cookie ヘッダーの値によって変わることを意味します。キャッシュは一意な Cookie の文字列ごとに別々のバリアントを保存するため、認証済みのユーザーがそれぞれ異なるセッション cookie を持つ以上、キャッシュの断片化が深刻になります。Vary: Cookie は cookie の値に本当に依存するレスポンス(パーソナライズされたページ、ダッシュボード)に限定し、公開された静的アセットには設定しないでください。

* (wildcard)

ワイルドカードの値は、リクエストヘッダーでは捉えられない要因によってレスポンスが変わることを示します。キャッシュはこれをキャッシュ不可として扱います。意図的に使われることはまれです。

例

サーバーがクライアントの要求したエンコーディングでレスポンスを圧縮します。Vary: Accept-Encoding ヘッダーは、Brotli に対応したクライアントに gzip で圧縮したレスポンスを配信してしまわないよう、gzip のリクエストと Brotli のリクエストで別々のコピーを保存するよう中間のキャッシュに伝えます。

Vary: Accept-Encoding

コンテンツネゴシエーションの構成が、同じエンドポイントから JSON または HTML を配信します。キャッシュは Accept ヘッダーの値に基づいて別々のレスポンスを保存します。

Vary: Accept

レスポンスに影響する複数のヘッダーは、カンマ区切りの値として列挙します。ここではレスポンスが、受け入れるエンコーディングと優先する言語の両方に依存しています。

Vary: Accept-Encoding, Accept-Language

ダイナミックサービングの構成が、モバイルとデスクトップのユーザーエージェントに異なる HTML を配信します。キャッシュは User-Agent の文字列全体をキーとして別々のバージョンを保存します。

Vary: User-Agent

Vary を Cache-Control と組み合わせることで、何によって変わるかと、各バリアントがどれだけ新鮮なままかの両方を制御できます。

Cache-Control: public, max-age=3600
Vary: Accept-Encoding

トラブルシューティング

Vary ヘッダーの欠落や広すぎる指定に起因するキャッシュの問題は、ユーザーが誤ったコンテンツを受け取る形で現れることがよくあります。

CDN が誤ったキャッシュのバリアントを配信する。サーバーがリクエストヘッダーに基づいて異なるコンテンツを返しているのに、それに対応する Vary の値を省略しています。CDN は最初のレスポンスをキャッシュし、リクエストヘッダーに関わらずすべてのクライアントに同じコピーを配信します。該当するヘッダー名を Vary のリストに加えてください。curl で異なるヘッダー値のリクエストを送り、X-Cache や Age のレスポンスヘッダーを比較してテストします。curl -H “Accept-Encoding: gzip” -v https://cdn.example.re/style.css と curl -H “Accept-Encoding: br” -v https://cdn.example.re/style.css のようにします。

Vary: User-Agent によるキャッシュの断片化。User-Agent の文字列には数千もの一意な値があります。Vary ヘッダーに User-Agent を列挙すると、ほぼすべての訪問者ごとに別々のキャッシュエントリが作られ、キャッシュヒット率が壊滅します。Cloudflare や Fastly のような CDN は User-Agent をデバイスのクラスへ正規化します。可能であれば、生の User-Agent ではなく Sec-CH-UA-Mobile のような正規化されたヘッダーや、CDN 固有のデバイス種別ヘッダーを使ってください。

Vary: * がキャッシュを完全に無効にする。ワイルドカードは、レスポンスがどのリクエストヘッダーにも現れない要因によって変わるとキャッシュに伝えます。ほとんどのキャッシュはこれをキャッシュ不可として扱い、保存したコピーを配信しません。そのリソースのキャッシュをすべて無効にする意図がない限り、Vary: * は削除してください。特定のリクエストヘッダーが差異を生んでいるのであれば、そのヘッダーを明示的に指定します。

Vary: Origin の欠落による CORS のキャッシュ汚染。サーバーが Origin リクエストヘッダーに基づいて動的な Access-Control-Allow-Origin の値を返しているのに、Vary: Origin を含めていません。CDN があるオリジンでレスポンスをキャッシュし、別のオリジンにも同じ CORS ヘッダーを配信するため、ブラウザがレスポンスをブロックします。動的なオリジンの値を反映するすべてのレスポンスに Vary: Origin を加えてください。診断するには、異なるオリジンから 2 つの curl リクエストを送り、キャッシュされた Access-Control-Allow-Origin が変わるかを確認します。curl -H “Origin: https://a.example.re” -v https://api.example.re/data と curl -H “Origin: https://b.example.re” -v https://api.example.re/data のようにします。

gzip と Brotli の間での Vary: Accept-Encoding の不整合。オリジンサーバーが gzip で圧縮する一方、CDN のエッジが Brotli へ再圧縮することがあり、キャッシュされたエンコーディングと Vary のキーの間に不一致が生じます。オリジンと CDN でエンコーディングの挙動を一致させてください。nginx では gzip_vary on; を設定すると Vary: Accept-Encoding が自動的に付加されます。gzip と併せて Brotli を使う場合は、エンコーディングごとに別々のエントリがキャッシュされるよう、両方のモジュールが同じ Vary の値を付加することを確認してください。