HTTP Vary Header — Keep Negotiated Responses from Crossing Paths
HTTP Vary Header — Keep Negotiated Responses from Crossing Paths
A single URL does not always produce one identical response. A server may choose a representation according to the request's accepted language, compression format, or media type. That flexibility is useful, but it creates a question for every cache in the path: when may a response generated for one request be reused for another?
The HTTP Vary response header is part of the answer. It does not turn caching on, set a freshness period, or choose the best representation. Instead, it tells a cache which request headers influenced the response, so the cache can avoid treating different variants as interchangeable.
One Resource, Several Representations
HTTP separates a resource from a representation of that resource. The resource might be identified by one URL, while the server has several possible representations: an HTML document in different languages, a compressed or uncompressed response, or an image in a format selected from the client's preferences.
This is called server-driven, or proactive, content negotiation. The client sends hints such as Accept, Accept-Encoding, or Accept-Language. The server applies its own selection algorithm and returns one representation. MDN's content-negotiation guide points out an important limitation: the algorithm is server-specific. The presence of a preference in a request does not by itself define which representation the server must choose.
Imagine a request for /guide with Accept-Language: id. The server might return Indonesian HTML. Another request for the same URL with Accept-Language: en might receive English HTML. The URL is the same, but the response is not.
What Vary Describes
Vary is a response header. Its value lists the request-header fields that influenced the response. A minimal example looks like this:
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Vary: Accept-Language
<html>...</html>
This says that the response selection depended on Accept-Language. It does not say that every possible language is available, and it does not tell the cache how the server's language-selection algorithm works. It identifies the input that the cache must consider when deciding whether a stored response can be reused.
The same idea applies to compression:
Vary: Accept-Encoding
If a server sends a compressed representation when the client accepts Brotli and a different representation otherwise, those requests must not be treated as the same variant merely because their URL and method match.
The formal definition is in RFC 9110, Section 12.5.5. The practical summary from MDN's Vary reference is useful: the header describes the parts of the request, apart from the method and URL, that influenced the response.
How a Cache Uses It
A cache already needs at least the request method and target URI to locate a stored response. For a response containing Vary: Accept-Language, that is not enough. The cache also compares the relevant Accept-Language field in the new request with the field in the request that produced the stored response.
RFC 9111, Section 4.1 describes this as a matching requirement. If the nominated request fields do not match, the cache must not reuse the stored response without revalidation. It will normally need to find another matching stored response or forward the request to the origin.
That gives us a useful mental model:
- The URL identifies the resource.
- The response's
Varyvalue names request fields that affected representation selection. - The cache compares those fields before reusing the response.
- Only a matching and otherwise reusable response can answer the new request.
This is not quite the same as saying that a cache key is literally a string made by concatenating the URL and header values. Implementations can have different internal designs. The protocol requirement is about whether the stored response is eligible for reuse, not about one prescribed data structure.
The Default Response Matters Too
A common mistake is to add Vary only when the request explicitly contains a preference. Consider a server that sends a default language when Accept-Language is absent, but sends a different language when the field is present. The default response is also the result of the same selection policy.
For that reason, the Vary value should be consistent for a URL's responses, including the default response. The cache specification warns that omitting it from a default response can allow that response to be selected for later requests even when a more suitable variant exists.
The same discipline applies to a 304 Not Modified response. A validator response does not carry a new representation body, but its metadata participates in the cache's update process. Keeping the relevant Vary metadata consistent is therefore not a cosmetic detail.
What Does Vary: * Mean?
The wildcard has a narrower and more consequential meaning:
Vary: *
It indicates that information other than the nominated request headers influenced the response. A cache cannot reproduce that selection using request-header matching alone. The HTTP caching rules treat a stored response with this wildcard as failing the normal variant match.
This is not a general-purpose way to list many headers. If Accept and Accept-Encoding influenced the response, name those fields explicitly:
Vary: Accept, Accept-Encoding
There is also a cost in the other direction. Adding many high-cardinality headers can create many variants and reduce the usefulness of a shared cache. MDN notes that server-driven negotiation can make shared caches less efficient and can expose more client preference information. The safest value is not the longest list; it is the smallest accurate list of fields that actually affected the representation.
Language Negotiation Is Not a Complete User Preference
Accept-Language looks convenient for multilingual websites, but it should be treated as a hint. A browser's value often reflects the language of its interface, not necessarily the language a person wants for every website. People may also access a site from a shared device or understand several languages.
MDN recommends giving the reader a way to override the server's choice, such as a language menu, and then respecting that explicit choice. This is a product and information-architecture decision, not something that Vary can solve.
For a site with stable language URLs, such as /en/guide, /id/panduan, and /de/anleitung, the URL itself can make the selected representation clear. That approach can reduce ambiguity for readers, links, bookmarks, and caches. It is not automatically better for every application, but it is often easier to reason about than silently changing one URL based on a browser hint.
A Review Checklist
When reviewing a negotiated response, I would ask these questions without assuming that a particular framework or CDN has made the right decision:
- Can the same URL return materially different representations?
- Which request fields actually influenced the selection?
- Does the response name those fields in
Vary? - Does the default response use the same relevant metadata?
- Do
304responses preserve the necessaryVaryinformation? - Would an explicit URL or user-selected setting be clearer than automatic negotiation?
- Are private or sensitive responses being considered separately from ordinary public variants?
This checklist cannot prove that a production cache is configured correctly. A reverse proxy, CDN, application framework, and browser may each have their own policies. It is a starting point for tracing the actual response headers and the request dimensions used by the application.
Conclusion
Vary is easy to overlook because it does not change the body a reader sees. Its job is to describe why that body was selected, so an intermediary does not reuse it for an incompatible request. It is therefore a correctness signal before it is a performance feature.
The practical lesson is modest: if a response changes because of a request header, make that dependency visible. Use explicit language URLs or an explicit user choice when they make the system easier to understand. And when the behavior depends on factors that cannot be represented by request headers, do not pretend that a cache can infer them from the URL alone.
References
- RFC 9110 — HTTP Semantics, Section 12.5.5: Vary, R. Fielding et al., June 2022.
- RFC 9111 — HTTP Caching, Section 4.1: Calculating Cache Keys with the Vary Header Field, R. Fielding et al., June 2022.
- Vary header — HTTP, MDN contributors, last modified November 21, 2025.
- Content negotiation — HTTP, MDN contributors, last modified December 21, 2025.
