One URL can have more than one correct response, and Vary is the header that tells an intermediary cache so. Mark Nottingham has called it the ugliest part of HTTP that hasn’t yet been improved — a “horrible, kludgy mechanism” with “pretty abysmal interoperability” between caches. Cloudflare added per-header handling for it to Cache Rules, and the reasoning behind the design is the interesting part.
Vary only half-solves the problem:
- It names the request fields that may affect a response. It says nothing about which differences actually change the response
- Origins collapse huge request spaces into a few representations — thousands of
Accept-Languagevalues onto three supported languages — while a cache only sees header bytes - Cardinality is multiplicative: 10 values on one field is 10 variants, 10 values across three fields is 1,000
- The failure mode is a cache that is perfectly correct and almost permanently cold: byte-identical bodies scattered across variants too cold to reuse, evicting each other, hit ratio down, origin traffic up
That isn’t a problem reserved for the careless. An analysis of more than 120 million responses from nearly 50,000 popular sites found almost 3,000 sites varying on four or more fields — some on 10, 23, or even 47.
The fix is a split of ownership. The origin still declares what may vary; the Cache Rule decides how much variation is worth caching, per header:
- normalize (the default): lowercase, sort by quality value, strip parameters for
Accept,Accept-Language, andAccept-Encoding, and optionally keep only the media types and languages the origin actually serves. Normalized values are forwarded to the origin, which is what keeps origin selection and cache matching in agreement - passthrough: raw bytes matter, including casing, whitespace, ordering, and duplicates — for controlled headers where the exact value changes the response
- bypass: don’t store the response when the origin names that header — for personalized or effectively unbounded values like
CookieandUser-Agent
Some caveats are worth carrying into any cache tier, not just Cloudflare’s. Purging a resource covers all of its variants; changing configuration does not purge existing entries. Normalization can silently drop q=0 — en-US;q=0 can become en — so origins that rely on “not acceptable” exclusions need passthrough. And because Vary is response-driven, every cacheable response that can differ by request fields has to return it consistently, including errors and fallbacks. One response that omits it can be cached without the isolation it needs.
Splitting one header into two decisions doesn’t make Vary elegant. It does turn a cardinality explosion from an accident into a choice.