How HTTP Works

Response header

< Vary: Accept-Encoding, Accept-Language

Vary Header: Cache Keys, Vary: Origin and Accept-Encoding

Vary lists the request headers a response depends on, so caches keep separate copies. Vary: Accept-Encoding, Vary: Origin for CORS, Vary: *, and CDN cache keys.

Reviewed 3 min readintermediate4 sourcesTry itMarkdown
Direction
Response
Category
Caching
Spec
RFC 9110 §12.5.5
On this page

TL;DR: Vary tells caches which request headers changed the response, so one URL can have several cached variants. Set Vary: Accept-Encoding when you compress and Vary: Origin whenever Access-Control-Allow-Origin depends on the request.

How a cache key really works

A cache keys on the method and URL, plus every request header named in the stored response’s Vary (RFC 9111 section 4.1). Two requests that differ only in an unlisted header share an entry.

GET /app HTTP/1.1
Accept-Encoding: br

HTTP/1.1 200 OK
Content-Encoding: br
Vary: Accept-Encoding

A later request with Accept-Encoding: gzip does not match, so the cache goes to the origin and stores a second variant. Matching is on the exact header values, so CDNs usually normalise Accept-Encoding first to avoid needless variants.

Headers you will actually vary on

  • Accept-Encoding. Compression. Servers add it automatically in nginx (gzip_vary on;) and Express compression middleware.
  • Origin. Dynamic CORS. See below.
  • Accept. API content negotiation (application/json vs text/html), and image formats such as image/avif.
  • Accept-Language. Language redirects without separate URLs; better to use /en/ paths.
  • Authorization or Cookie. Personalised responses. Better to mark them Cache-Control: private than to Vary; varying on Cookie fragments a shared cache into one entry per user.
  • User-Agent. Almost always a mistake. Use Client Hints or responsive CSS instead.

Vary: Origin and CORS cache poisoning

If your server echoes the caller’s origin:

Access-Control-Allow-Origin: https://app.example.com

then add:

Vary: Origin

Without it a CDN or the browser cache can store the response for https://app.example.com and serve it to https://admin.example.com, whose browser then logs:

The 'Access-Control-Allow-Origin' header has a value 'https://app.example.com' that is not equal to the supplied origin.

It often looks intermittent and disappears with a hard refresh. Also vary on origin even when you do not send an ACAO header for disallowed origins, otherwise a no-CORS response can be cached and reused for a CORS request. See Access-Control-Allow-Origin.

Vary: *

Vary: * says the response depends on factors outside request headers, such as client IP. Under RFC 9111 a cache never treats a stored response as a match without validation. It is blunt; Cache-Control: no-store or private states your intent better.

Configuration

nginx:

gzip on;
gzip_vary on;   # adds Vary: Accept-Encoding for compressed responses

location /api/ {
    add_header Vary Origin always;
}

nginx add_header Vary ... appends another Vary line rather than merging; that is allowed, as caches combine them. Check the combined result with curl.

Express:

res.vary('Origin')          // appends instead of overwriting
res.vary('Accept-Encoding')

Cloudflare: Vary support is limited (see the FAQ). To cache by origin, Accept or other headers, use a custom cache key via Cache Rules (Enterprise) or key on a URL parameter. CloudFront: forward the headers in the cache policy; they become part of the cache key. A Vary header that names a header CloudFront does not forward is a common cause of wrong content.

Verify

curl -sI https://example.com/app -H 'Accept-Encoding: br' | grep -iE '^(vary|content-encoding|age|x-cache|cf-cache-status)'
curl -sI https://api.example.com/data -H 'Origin: https://app.example.com' | grep -i '^vary'

Mistakes

  • Missing Vary: Accept when one URL serves JSON or HTML by negotiation, leaving the CDN to return whichever it cached first.
  • Vary: Cookie on public pages that merely read a tracking cookie; the hit rate drops to near zero.
  • Setting Vary in the app but having a proxy overwrite it. Inspect at the client, not just the origin.
  • Using Vary: User-Agent for mobile detection. Prefer one responsive page.

Frequently asked questions

What does the Vary header do?

It lists request header names that influenced the response. A cache must store a separate variant per distinct value of those headers and reuse a stored response only if the new request matches on every listed header.

Why do I need Vary: Accept-Encoding?

The same URL returns different bytes for gzip, Brotli or identity. Without Vary: Accept-Encoding a shared cache can serve a compressed body to a client that did not ask for it, or the reverse. Most servers add it automatically when compressing; CDNs normalise the header into a small number of variants.

Why do I need Vary: Origin for CORS?

If Access-Control-Allow-Origin echoes the request Origin, the response differs per origin. Without Vary: Origin a cache can serve the copy generated for origin A to origin B, producing a CORS error that vanishes on hard refresh. Add it whenever the allowed origin is dynamic.

What does Vary: * mean?

It means the response depends on something beyond request headers, so no stored response can be matched to a later request. Caches may store it but must revalidate every time. Prefer Cache-Control: no-store when you want to stop caching.

Why is my CDN cache hit rate low with Vary?

Each distinct value of a varied header creates a new cache entry. Vary: User-Agent or Vary: Cookie explodes into thousands of variants. Vary only on headers with a few values, or normalise them at the edge into buckets.

Does Cloudflare honour Vary?

Not generally. On most plans Cloudflare ignores Vary except Accept-Encoding, and supports Vary for images only in specific configurations. Do not rely on Vary: Origin to protect an edge-cached CORS response; include the origin in a custom cache key or bypass caching for those paths.

Sources

  1. MDN Web Docs: Varydeveloper.mozilla.org
  2. RFC 9110 section 12.5.5: Varyrfc-editor.org
  3. RFC 9111 section 4.1: Calculating Cache Keys with Varyrfc-editor.org
  4. Fetch Standard: CORS and cachingfetch.spec.whatwg.org

Keep going

Browse /search