Vary Header

Caching

Vary is the response header that names the request headers the origin used to select a representation, so caches keep one copy per distinct combination of their values (RFC 9110 section 12.5.5). Vary: * can never be matched from cache; Cookie or User-Agent shatter the hit ratio.

Also known as Vary, Vary header field.

12 min read Updated Aug 30, 2026

Full Explanation

Vary is a response header. It names the request headers the origin server used to choose which representation to send. Caches then store and serve one copy per distinct combination of those values. Vary is the caching half of HTTP content negotiation. RFC 9110 section 12.5.5 defines it. RFC 9111 section 4.1 turns it into cache behaviour. Vary does not change the body or the freshness lifetime. It only widens the cache key. This way, responses tailored to different preferences cannot overwrite or shadow one another. Servers send it, clients never do, and there is no request-side counterpart. Read a Vary value as a multiplier on the number of stored copies, not as an on/off switch. There is one exception: Vary: * means a stored response can never be matched at all. Every request then has to reach the origin.

How it works

An origin may tailor a response to the request. It then adds Vary: field-name, field-name. This names the request headers that had a role in the choice. RFC 9110 section 12.5.5 says an origin server SHOULD generate Vary on a cacheable response when it wants that response to be selectively reused. This is typically the case when the origin picked the language, the coding, or the media type from the client's stated preferences. Vary: Accept-Encoding, Accept-Language indicates the origin might have used those two request fields, or their absence, as determining factors. From then on:

  1. The cache key gains the values of every request header the response's Vary lists. At a minimum, the cache key is composed from the request method and the target URI (RFC 9111 section 2). RFC 9110 puts it plainly: Vary expands the cache key required to match a new request to the stored cache entry.
  2. For a later request, the cache MUST NOT use that stored response without revalidation. This rule applies unless all the presented request header fields nominated by the Vary value match those fields in the request that caused the response to be stored (RFC 9111 section 4.1). A mismatch is not an error. It is a miss, and the cache normally forwards the request to the origin.
  3. Matching is deliberately loose. Two sets of fields match if one can be transformed into the other. Three transformations qualify: adding or removing whitespace where the field syntax allows it, combining repeated field lines of the same name, or applying a normalization known to have identical semantics under that field's own specification. Examples of that normalization include reordering values where order is not significant, or case-normalizing values defined to be case-insensitive. A field absent from one request matches only a request where it is also absent.
  4. When several stored responses match, a nominated header MAY be used to pick one. This applies when the header has a known mechanism for ranking preference, such as qvalues on Accept and similar fields. Where no such mechanism applies, or where candidates rank equally, the cache chooses the most recent response by Date.

The spec gives Vary two purposes. First, it tells caches they must not satisfy a later request from this response unless the listed fields carry the same values, or unless the origin has validated the reuse. Second, it tells user agents that the response was content-negotiated, so a later request with other values may get a different representation. The cost is multiplicative. With Vary: Accept-Encoding, Accept-Language, two codings times four languages is up to eight stored copies of one URL. RFC 9110 accepts that tradeoff explicitly. Vary “might be elided when an origin server considers variance in content selection to be less significant than Vary's performance impact on caching”.

Here is how that looks in real responses.

// Compression negotiation
HTTP/1.1 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding
Cache-Control: max-age=86400

// Language negotiation
HTTP/1.1 200 OK
Content-Language: de
Vary: Accept-Language
Cache-Control: max-age=3600

// Several selecting headers in one Vary
// (the cache stores one copy per combination of values)
HTTP/1.1 200 OK
Content-Encoding: br
Content-Language: en
Vary: Accept-Encoding, Accept-Language
Cache-Control: max-age=3600

// Cache-killing: never send either of these on cacheable content.
// Each line below is its own example. Sent in one response they would
// combine into "Vary: Cookie, *" (RFC 9110 section 5.3).
Vary: Cookie
Vary: *

Why it matters for a CDN

A CDN is a shared cache. One URL is requested by clients whose Accept-Encoding and Accept-Language values differ. Each client must receive a representation it can actually use. A content coding is acceptable to a client only if the client listed it without a qvalue of 0. An uncoded representation is acceptable by default, unless the client excluded identity (RFC 9110 section 12.5.3). Without Vary, the cache has no way to keep those variants apart. It can then hand a gzip- or Brotli-coded copy to a client that accepted no coding at all. With Vary present, RFC 9111 section 4.1 forbids reuse across mismatched values. The cache may reuse it only if the copy is revalidated with the origin first.

Vary is therefore how a CDN serves negotiated variants safely from a single URL. It also sets the cache hit ratio directly. Each new combination of values is a separate object. Its first request is a miss and an origin fetch. The list of headers decides how many copies of every object the edge has to hold and keep fresh.

What CDNs do

Every major CDN implements Vary. But they differ in three ways: how strictly values are matched, whether normalization is offered, and whether the origin's header alone is enough to create variants.

  • Cloudflare: the request headers an origin lists in Vary become part of the cache key for that response, following RFC 9111. This applies only to headers you configure in Cache Rules. Cloudflare does not vary every cached response merely because Vary is configured. It requires both the configuration and a Vary header on the origin response. Each configured header carries an action. Normalize folds equivalent values. It is optional but recommended, and it is the answer for most Accept, Accept-Language and Accept-Encoding cases. Passthrough keeps the raw value, so reordered but equivalent values become separate copies. Bypass means Cloudflare does not store the response. It is intended for headers with too many possible values, per-user values, or values you do not want cached. A response containing Vary: * always bypasses cache regardless of configuration. Purging a URL purges all cached versions of it, with no separate purge per Vary value. By default, Cloudflare also overrides Accept-Encoding towards the origin to gzip or gzip, br, and it recompresses cached assets for each visitor, unless Respect Strong ETags is enabled (Cloudflare Vary docs).
  • Fastly: supports Vary per spec. It frames the listed headers as “a kind of secondary cache key” on top of its default request-URL-and-Host key. Its caching best practices tell you to use Vary rather than manipulate the cache key directly. The limits are hard, and they differ by platform. In CDN services, the number of variants is limited to 50 per cache object, regardless of how many Vary rule permutations exist. In Compute services, the variant count is not limited, but distinct vary rules are capped at 8 per cache object (Vary reference, caching concepts, caching best practices).
  • Amazon CloudFront: variants come from the cache policy rather than from the origin's header. By default, the cache key is the distribution domain name and the URL path. Other viewer-request values are included only when you add them to a cache policy. So naming a header in Vary does not by itself produce per-header copies. The wildcard behaviour is Minimum-TTL dependent. If the origin returns Vary: * and Minimum TTL is 0, CloudFront caches the object but forwards every subsequent request to the origin to confirm freshness, without conditional headers. The origin then returns the whole object every time. With any other Minimum TTL, CloudFront processes Vary as one of the response headers it may forward to the viewer. AWS advises against keying on Date or User-Agent, because those headers have many possible values. AWS suggests distinct URLs per language instead of varying on Accept-Language (origin request and response behaviour, understand the cache key).
  • Varnish: stores one object per distinct value of each varied header, and it compares those values exactly. So “en-us, en-uk” and “en-us,en-uk” are two objects, and a difference in casing alone splits the cache. Its own guide calls Vary: User-Agent a pitfall, and it tells you to normalize the header if you truly must vary on it. Varnish answers with a 503 when it fails to parse Vary, or when any client header listed in Vary exceeds its limit of 65k characters (Varnish user guide; see Varnish).

Watch out for

  • Vary: *: a stored response whose Vary value contains “*” always fails to match (RFC 9111 section 4.1). So nothing is served from cache without reaching the origin. The wildcard signals that other aspects of the request may have selected the representation. This can include aspects outside the message syntax, such as the client's network address. RFC 9110 section 12.5.5 forbids a proxy from generating it. One origin misconfiguration turns a cached route into a pass-through.
  • High-cardinality headers: Cookie, User-Agent and anything per-user create one variant per distinct value. The number of distinct values is effectively unbounded. So the hit ratio collapses, and the origin absorbs the traffic. Varnish documents User-Agent as a pitfall. Cloudflare's bypass action exists for exactly these headers. CloudFront warns off Date and User-Agent for the same reason.
  • Multiplication and platform caps: every listed header multiplies the stored copies. The ceilings are real rather than theoretical. Fastly, for example, stops at 50 variants per object in CDN services. Keep the list to headers that genuinely change the body.
  • Exact versus normalized matching: RFC 9111 permits semantically safe normalization, but it does not require it. Varnish compares byte-for-byte, so whitespace or case differences fragment the cache. Cloudflare, by contrast, normalizes Accept, Accept-Language and Accept-Encoding when that action is configured. Do not assume two CDNs will produce the same number of variants from the same traffic.
  • Vary missing from the default representation: RFC 9111 section 4.1 notes that resources which mistakenly omit Vary from their default response get that response chosen for later requests, even when more preferable ones are stored. Where a cache holds several responses for one URL, and one or more omits Vary, the cache SHOULD choose the most recent stored response that has a valid Vary value. Mixing Vary values across deploys can therefore serve the wrong variant.
  • 304 responses: a server generating a 304 (Not Modified) MUST include Vary if a 200 to the same request would have carried it. This applies alongside Content-Location, Date and ETag (RFC 9110 section 15.4.5). Dropping Vary on the revalidation path is a common origin bug.
  • Authorization: do not list it. RFC 9110 section 12.5.5 says there is no need: reuse of that response for a different user is already prohibited by the field definition, so Vary only fragments the cache. The same section makes a parallel point about network region. If content was selected by region, but you want the cached response reused as clients move between regions, keep region out of Vary.

Best practice

  • Send the same Vary on every representation of a negotiated resource. This includes the default one, and 304 responses. No cache can then fall back to a copy with a missing or narrower Vary value.
  • Vary only on headers that change the body. Prefer low-cardinality ones, such as Accept-Encoding and Accept-Language. Treat Vary: Cookie and Vary: User-Agent as bugs to fix, not settings to ship. If a resource is genuinely per-user, mark it Cache-Control: private instead.
  • Normalize what reaches the cache key: qvalues, casing, ordering, region subtags. Do this in the origin, at the edge, or through the platform's normalize action, so equivalent requests collapse into one variant. Where the CDN also forwards the normalized value upstream, the origin generates content that matches the key the response will be stored under.
  • Consider not varying at all when the variance is structural. Distinct URLs per language or per format give one object each, with no secondary key. This is what AWS recommends over varying on Accept-Language. RFC 9110 explicitly allows eliding Vary when its caching cost outweighs the variance.
  • After any Vary change, prove the variants are isolated. Request the URL twice with different values for the header. Compare the Age and cache-status headers you get back, and watch the hit ratio. Changing Vary configuration does not itself purge what is already cached. Requests may therefore miss and refill under the new keys until old entries expire or are purged.

Examples

This example shows how Vary builds the cache key. The same URL makes different cache entries for different request headers.

# Same URL, different cache entries due to Vary: Accept-Encoding
curl -H "Accept-Encoding: gzip" https://cdn.example.com/api/data
# Cache key: GET|cdn.example.com|/api/data|gzip

curl -H "Accept-Encoding: br" https://cdn.example.com/api/data
# Cache key: GET|cdn.example.com|/api/data|br

curl -H "Accept-Encoding: identity" https://cdn.example.com/api/data
# Cache key: GET|cdn.example.com|/api/data|identity

Frequently Asked Questions

Vary is the response header that names the request headers the origin used to select a representation, so caches keep one copy per distinct combination of their values (RFC 9110 section 12.5.5). Vary: * can never be matched from cache; Cookie or User-Agent shatter the hit ratio.

This example shows how Vary builds the cache key. The same URL makes different cache entries for different request headers.

# Same URL, different cache entries due to Vary: Accept-Encoding
curl -H "Accept-Encoding: gzip" https://cdn.example.com/api/data
# Cache key: GET|cdn.example.com|/api/data|gzip

curl -H "Accept-Encoding: br" https://cdn.example.com/api/data
# Cache key: GET|cdn.example.com|/api/data|br

curl -H "Accept-Encoding: identity" https://cdn.example.com/api/data
# Cache key: GET|cdn.example.com|/api/data|identity

Yes. Vary Header is also known as Vary, Vary header field. Vary is the response header that names the request headers the origin used to select a representation, so caches keep one copy per distinct combination of their values (RFC 9110 section 12.5.5). Vary: * can never be matched from cache; Cookie or User-Agent shatter the hit ratio.