A cache normally stores a response under the request URL. What does the HTTP response header Vary add to that, and how does a shared cache use it when a later request arrives for the same URL?
answer
- Primary key = method + URL
- Vary = secondary cache key
- Response header naming request headers
- Every header multiplies variants
- Vary: * never reuse
basics
~20 sVary is a response header listing the request headers the server used to choose this response. The cache keys the entry on the URL plus those header values, so it reuses the stored copy only when a new request has matching values.
solid answer
~50 sA cache's **primary key** is method plus URL. That is wrong whenever the server negotiates content, because one URL can yield different bytes depending on request headers (gzip vs plain, English vs German). `Vary` is the response header in which the origin names those request headers; RFC 9111 calls them the **secondary cache key**. On store, the cache records the response together with the values those headers had in the original request. On lookup, it may reuse that stored variant only if every header named in `Vary` matches in the new request; otherwise it forwards to the origin and may store a second variant under the same URL. The origin sets it; every cache in the path enforces it — browser private cache, forward proxy, reverse proxy, CDN. Clients cannot ask for it. Each extra header multiplies the number of variants, so `Vary` is both necessary and expensive: correct negotiation, lower hit rate.
code
http · 9 linesGET /reports/2026 HTTP/1.1
Host: api.example.com
Accept-Encoding: gzip
HTTP/1.1 200 OK
Content-Type: application/json
Content-Encoding: gzip
Cache-Control: max-age=300
Vary: Accept-Encodinggo deeper
Be able to say Vary is a response header, that it lists request headers, and that the cache key becomes URL plus those header values.
Explain store-versus-lookup matching, name the canonical Accept-Encoding case, and mention that each header multiplies stored variants.
Discuss hit-rate cost, normalization, high-cardinality headers, and how Vary interacts with revalidation and 304 responses.
Frame Vary as cache-key design: which axes of variation are worth a separate object, where negotiation should live (origin, edge, or separate URLs), and the operational blast radius of getting it wrong.
## The problem it solves An HTTP cache stores a response and later replays it instead of contacting the origin. To know which stored response answers which request it needs a key. The default — the **primary cache key** — is the request method plus the target URL. That is enough only when a URL always produces the same bytes. Content negotiation breaks that. The same URL can legitimately return different representations depending on request headers: a gzip-compressed body for a client that sent `Accept-Encoding: gzip` and an uncompressed one otherwise; a German body for `Accept-Language: de`; JSON or XML depending on `Accept`. If the cache keyed only on the URL it would store whichever variant it saw first and hand it to everyone — compressed bytes to a client that cannot decompress them, German to an English speaker. ## Vary is the secondary cache key `Vary` is a **response** header carrying a comma-separated list of **request** header field names that the server consulted when selecting or generating this response. RFC 9111 calls those fields the secondary cache key. - **On store:** the cache saves the response plus the values those named headers had in the request that produced it. - **On lookup:** for a new request with the same primary key, the cache walks its stored variants. A variant is usable only if, for every field name in that variant's `Vary`, the new request's value matches the stored one. Matching is field-specific in theory; in practice most caches normalize whitespace and case and then compare bytes, which is why unnormalized values fragment the cache. - **On miss:** the request goes to the origin, and the new response may be stored as an additional variant under the same URL. `Vary: *` is special: it means selection depended on something not expressible as a header, so no stored response may ever be reused for a later request. ## Who sets it, who enforces it The origin server (or the reverse proxy/CDN acting for it) emits `Vary`. Every cache along the path enforces it: the browser's private cache, a corporate forward proxy, a shared CDN edge, a reverse proxy such as nginx or Varnish. It is not a request header — a client cannot ask a cache to vary on something — and it is advisory only in the sense that a broken intermediary may ignore it, which is one reason `no-store` rather than `Vary` is the tool for genuinely private data. ## Typical values `Accept-Encoding` (compression — the canonical case), `Accept-Language`, `Accept`, `Origin` (CORS reflection), `X-Device-Type` or similar normalized device buckets. Dangerous values are ones with huge cardinality — `User-Agent` and `Cookie` — because they approach a unique key per client. ## The cost model Every header added to `Vary` multiplies the number of stored variants for that URL: two encodings times three languages is six objects to fill, each with its own miss to the origin. Hit rate falls roughly with the product of the cardinalities. So the rule is: vary on exactly the headers that actually change the bytes, and normalize those headers to the smallest set of distinct values you can (for example, collapse any `Accept-Encoding` mentioning gzip to the single token `gzip`). ## Revalidation and 304 When a stored variant goes stale, the cache revalidates using a conditional request that carries the same varying headers as the original, so the origin selects the same variant. A `304 Not Modified` then refreshes that specific variant. Servers should send `Vary` on the 304 as well, and if the `Vary` value itself changes, caches treat previously stored variants as no longer matching. ## What it does not do `Vary` says nothing about *how long* something may be cached or *whether* it may be cached at all — that is the job of the freshness directives. `Vary` only decides *which* stored copy may answer *which* request.
- If a server negotiates content but sends no Vary header at all, what breaks?The cache treats all requests for that URL as equivalent and serves whichever variant it stored first. A client that never advertised gzip can receive a gzip body it will not decode, or an English speaker gets the German page. The bug is intermittent and depends on who warmed the cache, which makes it hard to reproduce.
- Does Vary matter for a browser's private cache the same way as for a CDN?The mechanism is identical, but the impact differs. A private cache serves one user, so varying on Cookie or User-Agent costs almost nothing because those values rarely change. In a shared cache the same Vary can push the hit rate to nearly zero, because every user contributes a distinct secondary key.
A coat check with one hook number per ticket, plus a note saying 'also check the size and colour on the ticket' — the number alone no longer identifies the right coat.
saying these in an interview costs you the question
- Saying Vary lists response headers, or that it is a request header the client sends
- Believing Vary controls freshness or forces revalidation
- Treating Vary: * as a safe default rather than a total opt-out of reuse
- Assuming Vary makes a response private — it does not; only no-store/private do
- Adding headers to Vary 'just in case' without accounting for the variant explosion