Vary: Origin, why CORS breaks behind a cache without it (tested)

Vary tells caches which request headers change the response. Leave out Vary: Origin on a CORS response that echoes the Origin, and a cache will serve one site's answer to another.

1. The bug, reproduced

Backend: Access-Control-Allow-Origin: $http_origin (echo the caller), Cache-Control: max-age=60. In front: an nginx cache. Three requests to the same URL:

Request OriginWithout VaryWith Vary: Origin
https://a.exampleACAO a.example (MISS)ACAO a.example (MISS)
https://b.exampleACAO a.example (HIT)ACAO b.example (MISS)
https://a.exampleACAO a.example (HIT)ACAO a.example (HIT)

Without Vary, the cache stored the first answer and served it to b.example. A browser on b.example rejects that response ("The 'Access-Control-Allow-Origin' header has a value 'https://a.example' that is not equal to the supplied origin"), so the bug looks random: it depends on who filled the cache first. With Vary: Origin nginx stored one copy per Origin and each caller got its own header.

2. When you need Vary: Origin

# nginx
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;

Our multiple-origins CORS guide builds the $cors_origin allow-list map this snippet uses.

3. Vary in general

Vary lists request headers that select between response variants: Vary: Accept-Encoding (gzip vs plain, the most common), Vary: Accept-Language, Vary: Cookie. Each listed header splits the cache, so Vary: Cookie or Vary: User-Agent can make a CDN cache almost useless. Some CDNs ignore or normalise certain Vary values; check your CDN's documentation. Inspect any URL's Vary with curl -sI or the free HTTP headers tool.

Frequently asked questions

What does the Vary header do?

It tells caches which request headers the response depends on, so they keep one copy per value. In our nginx test, Vary: Origin made the cache store separate copies for a.example and b.example.

Why do I need Vary: Origin for CORS?

If the server echoes the Origin into Access-Control-Allow-Origin and the response is cached, the next site gets the first site's header. Our nginx cache did exactly that without Vary: Origin.

Do I need Vary: Origin with Access-Control-Allow-Origin: *?

No. The response is the same for every origin, so there is nothing to vary on.

Is Vary: Accept-Encoding needed?

When the server sends gzip/brotli to some clients and plain text to others, yes; servers like nginx add it for compressed responses when gzip_vary is on.

Written with AI assistance. Measured on 2026-10-01 with curl 8.14.1 against nginx 1.31.6 in a throwaway container: an nginx proxy_cache in front of a backend that echoes the request Origin into Access-Control-Allow-Origin with Cache-Control: max-age=60, once without and once with Vary: Origin. CDN behaviour and the quoted browser error (Chrome's standard CORS message) are not part of this test. PlainHeaders is our extension; curl and the HTTP headers tool are free.