Yadis

Identity, trust and the plumbing of the web·on the home of the Yadis discovery protocol since 2005

§02  Web Standards

HTTP Content Negotiation: When to Use the Vary Header

HTTP Content Negotiation: When to Use the Vary Header

Shlama Syriac Aramaic word for peace / SchnitzelMannGreek., CC BY-SA 3.0

The Vary response header is the pivot point in HTTP content negotiation. Negotiated content can only be cached if the server explicitly directs caches which headers influenced the response influenced the response. But it's exactly that direction that makes the system fragile.

As a protocol, HTTP lets a server select among representations for a resource using request headers like Accept-Language and Accept. The Vary header is pivotal: it tells caches, CDNs, and clients which request headers the server considered in generating the response, so they can reuse that response for requests with the same values. https://datatracker.ietf.org/doc/html/rfc9110 https://datatracker.ietf.org/doc/html/rfc9110 https://datatracker.ietf.org/doc/html/rfc9110 https://datatracker.ietf.org/doc/html/rfc9110 https://httpd.apache.org/docs/current/content-negotiation.html https://developers.cloudflare.com/cache/concepts/vary/

When negotiation holds up best is when a server can serve variant representations of a single resource from the same URL. Most often this is for delivering language-localized content or negotiating media types like HTML, JSON and XML.

If a server selects HTML, JSON, or XML based on the Accept request header, responses can be reused by caching intermediaries when the same Accept header is present in subsequent requests.

Here illustration of what the HTTP server can do:

Accept: text/html .. Accept: application/json

``jsp { "some": "data" } `

Accept: application/xml

`xml .. `

The Vary header makes this possible by tagging the response with the headers that were decisive for the server’s choice. For instance, the Vary: Accept header grants reusability across any request carrying the same value of that Accept header.

However, the key challenge with Vary is that it changes the algorithm that caches and CDNs use to select a representation for a client’s request: the cache key becomes a function not just of the URL, but of whichever headers the Vary header lists.

When the server imposes caching on intermediaries via Vary, some immediate consequences kick in:

It can be safer and simpler, then, to serve distinct URLs for variant representations of a resource. Instead of serving /data as HTML, JSON and XML via Accept: text/html, Accept: application/json and Accept:application/xml, the server could serve /data.html, /data.json and /data.xml—distinct endpoints for each syntactic representation.

URL separation means users can share a link that reliably delivers the intended representation—no need to test the link or debug an unintended Vary check. A fixed URL wouldn’t be indexed under a Vary value, and wouldn’t split a cache at the whim of server config or negotiator input. Each URL is an isolated cache key.

But Vary remains a crucial part of HTTP’s basic tools. If a server selects representations based on input headers, it dictates on one side that such negotiation is safe, and on the other marks the conditions under which a stale but OK cache response can be reused. Negotiation is flexible where URL separation fixes URLs. It still matters which HTTP standard dictates it: the core IETF specifications or particular implementations. https://developers.cloudflare.com/cache/concepts/vary/ https://www.hjp.at/doc/rfc/rfc2295.html https://www.hjp.at/doc/rfc/rfc2295.html https://developers.cloudflare.com/changelog/post/2026-07-02-vary-for-cache-rules/ https://developers.cloudflare.com/cache/concepts/vary/

Even after all its evolution, Vary remains the core contract between a server negotiating responses on the fly and the caches storing those snapshots for later reuse. So long as HTTP keeps that contract—so long as servers trumpet the Accept` headers distinguishing the deltas between client requests—caching remains sound.