Article summary — original

Cloudflare Adds HTTP Vary Header Support to Cache Rules Across All Plans Cloudflare has introduced support for the HTTP Vary response header across all plan tiers via Cache Rules. The feature lets origins define which request headers influence responses while giving site operators control over whether the CDN normalizes header values, passes them through raw, or bypasses caching entirely. Vary is an HTTP standard header indicating which request fields alter an origin's response, preventing caches from serving incorrect formats (such as HTML instead of JSON) for identical URLs. Unmanaged Vary headers frequently cause cache fragmentation and cold caches due to subtle variations in whitespace, case, header order, and quality values across incoming requests. Cloudflare's new Cache Rules implementation provides three distinct handling actions per Vary header: normalize, passthrough, and bypass. Normalized Accept and Accept-Language headers are forwarded directly to origins before response caching to keep origin variant selection aligned with CDN cache keys. Vary: * consistently bypasses caching regardless of user configuration, and purges against a base resource automatically invalidate all its cached Vary variants. The Challenge with HTTP Vary The HTTP Vary response header allows a single URL to deliver different representations, such as localized languages or alternative content types. However, because origins often collapse thousands of distinct header variations into a few supported outputs, intermediary caches traditionally treat minor syntactic differences as unique entries. Formatting quirks, header ordering, and varying quality parameters create high cardinality that scatters identical responses across cold cache entries, evicting reusable data and lowering hit ratios. Configurable Cache Rule Actions To prevent cache fragmentation without ignoring legitimate variation, Cloudflare divides handling into three actions configured in Cache Rules. 'Normalize' aligns common negotiation fields such as Accept, Accept-Language, and Accept-Encoding by lowercasing values, sorting by quality parameters, stripping optional whitespace, and reducing regional language tags. 'Passthrough' preserves raw bytes for headers with small, controlled sets of values where exact formatting changes output. 'Bypass' prevents storage for unpredictable, personalized, or high-cardinality headers like Cookie or User-Agent, while responses returning Vary: * always bypass cache. Request Flow and Origin Alignment When processing requests, Cloudflare applies normalization rules before contacting the origin and forwards normalized Accept and Accept-Language values upstream. This prevents discrepancies between the origin's response selection and the CDN's cache-matching logic. When the origin answers with a Vary header, Cloudflare records the specified fields alongside the base cache key to store or match cached variants directly on subsequent requests, rather than evaluating entries sequentially. Cache Management and Deployment Purging a cached resource automatically invalidates all of its associated Vary variants, although altering Vary rules does not retroactively clear existing cache keys. Cloudflare contrasts Vary with custom cache keys: custom keys apply designated dimensions uniformly across every covered response, whereas Vary allows origins to declare variations dynamically on a per-response basis. The feature is available immediately on Free, Pro, Business, and Enterprise plans via the dashboard, Rulesets API, and Terraform.

Article summary — zh

Cloudflare 在 Cache Rules 中全面上线 HTTP Vary 响应头支持 Cloudflare 宣布在所有套餐(Free、Pro、Business、Enterprise)的 Cache Rules 中正式支持 HTTP Vary 响应头,使用户能够灵活控制多表征内容缓存匹配,避免由于客户端微小标头差异导致的缓存碎片化与命中率下降。 源站通过 Vary 标头指示影响响应的请求字段,但原始标头的细微格式差异极易导致缓存过度分裂甚至失效。 Cloudflare 为 Vary 引入了 normalize(规范化)、passthrough(原样透传)和 bypass(绕过缓存)三种处理动作,并强制设定 Vary: * 绕过缓存。 normalize 动作会自动处理 Accept、Accept-Language 和 Accept-Encoding 的大小写、权重排序并转发给源站,以保证源站生成逻辑与缓存键严格一致。 针对全局固定维度建议采用自定义缓存键(Custom Cache Key),而针对源站动态声明响应变化的场景则应使用 Vary。 Vary 机制的痛点与设计挑战 HTTP Vary 允许单 URL 返回多种内容(如不同语言或格式),但标准未定义请求字段的哪些差异切实相关。客户端在空白符、大小写、质量值权重(q 值)或语言子标签上的细微区别,会导致中间件为同一实际内容生成成千上万个极少复用的缓存变体,严重降低命中率并增加源站负载。研究显示近 3000 家热门网站对 4 个以上字段使用 Vary,极易造成缓存污染。 Cache Rules 中的三种处理动作 Cloudflare 将决策分为源站声明与用户控制两步,针对 Vary 列出的字段提供三种策略:normalize(规范化)适合协商标头,将海量请求折叠到有限响应;passthrough(透传)按原始字节匹配,适用于严格取决于精确值的有限集合;bypass(绕过)适用于高基数或个性化字段(如 Cookie 或 User-Agent)。若源站返回 Vary: *,系统将一律绕过缓存。 规范化执行逻辑与配置实践 在 normalize 模式下,系统会在首次回源前将 Accept、Accept-Language 等转为小写并按权重排序,甚至可配置白名单仅保留支持的语言和格式,同时将规范化结果直接转发给源站以保持对齐。清除某 URL 缓存将覆盖其所有 Vary 变体。该功能已通过控制台、Rulesets API 以及 Terraform 全面开放配置。

Saved original article

The response header, `Vary`, has been called “[the ugliest part of HTTP that we haven't yet improved.](https://mnot.net/blog/2026/linting_the_web)” The same post describes it as a “horrible, kludgy mechanism” with “pretty abysmal interoperability” across intermediaries. That is usually where sensible engineers back away slowly with their hands raised. That’s not exactly an endorsement of `Vary`, but ugly doesn’t mean useless. One URL can have more than one correct response. A server might, for example, deliver different image formats to different browsers. If a cache ignores `Vary`, it risks serving the wrong bytes to a request. But if it treats every raw header value as distinct, a handful of similar requests can spread into thousands of barely reusable cache entries. `Vary` tells a cache which request fields may affect the response, but it does not tell the cache which differences *actually matter.* `Vary` support is now available in [Cache Rules](https://developers.cloudflare.com/cache/how-to/cache-rules/settings/#vary) on every plan. The origin still names the request headers that may affect a response, but you decide how Cloudflare handles each one. You can normalize known negotiation headers, pass exact values through when those small differences matter, or bypass cache when the variation is too unpredictable. The origin declares what may vary, and you decide how much variation is actually meaningful for the cache. `Vary` is a [standard](https://www.rfc-editor.org/rfc/rfc9110.html#name-vary) HTTP response header that tells intermediary caches (like Cloudflare) which request fields may affect the response sent by the origin. Sites use `Vary` to serve different languages, image formats, compression schemes, or regional content from the same URL. Take one URL that produces two valid representations. A browser requests a webpage: The origin returns HTML and identifies `Accept` as a field that may affect the response: An API client can request the same URL with a different preference: This time, the correct response is JSON. The `Vary: Accept` header tells the cache that the URL alone is not enough to choose between responses. The request’s `Accept` value must also be considered. Without `Vary`, whichever response enters the cache first can be served to both clients. If HTML wins, the API client receives markup and its JSON parser fails. If JSON wins, a browser expecting a web page receives an API response. `Vary` prevents the cache from serving the wrong response to the requesting client. But it introduces a harder question: when two requests contain different header values, do they actually *need* different responses? Vary can tell a cache which request fields may affect a response. It does not tell the cache what the response represents. For example, take an origin that serves content in only English, French, and German. A client might send: While another client might request: Both requests prefer English here. The origin’s response may map both requests to exactly the same English response. But a cache comparing the raw values cannot safely assume they are equivalent. They have different orders and [language tags](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Language#directives) (that the origin doesn’t differentiate). So the cache may store them as separate variants, even when their response bodies contain identical bytes. This is `Vary`’s central problem. Applications often produce a small, finite set of representations from an enormous set of possible request values. The origin understands that thousands of language preferences collapse into three supported languages, while a cache usually does not. This problem compounds when a response varies on multiple fields. Ten possible values across one field create ten variants. Ten values across three fields can create 1,000 combinations. Real headers can have far greater cardinality: `User-Agent` values are numerous, cookies can be unique to individual visitors, and preference headers can differ in ordering, formatting (spaces and tabs matter!), and [quality values](https://developer.mozilla.org/en-US/docs/Glossary/Quality_values). The result is a cache that can be perfectly correct and almost permanently cold (an entry never reused). Identical responses can be scattered across entries that receive too little traffic to remain hot and in cache. They can consume capacity, [evict](https://developers.cloudflare.com/cache/concepts/retention-vs-freshness/#retention) one another, reduce cache hit ratios, and send more requests back to origin servers. Eviction can remove cold entries, but it cannot merge them just because the responses are identical. [An analysis](https://mnot.net/blog/2026/linting_the_web) of more than 120 million responses from nearly 50,000 popular sites found almost 3,000 sites varying on four or more fields. Some varied on 10, 23, or even 47 fields. We want to make sure that customers have the tools they need to use `Vary` when appropriate, but not so much that they create a useless cache. Some high-cardinality variation is deliberate. CDNs or reverse proxies may inject values, such as a geographic region, to partition content predictably. That works when the possible values are controlled and every component agrees on their meaning. Without those constraints, the cache fragments into variants it may never reuse. That was the design problem we needed to solve to support `Vary`. We needed to preserve enough variation to serve the right response, without allowing incidental differences between requests to destroy cache efficiency. Cloudflare customers already had several ways to handle negotiated content similar to `Vary`. They could bypass cache and let their origin deal with it, reproduce the origin's negotiation logic in a [custom cache key or other rule](https://developers.cloudflare.com/cache/how-to/cache-rules/), use a [Worker](https://developers.cloudflare.com/workers/cache/#content-negotiation-with-vary), or use features like [Vary for images](https://developers.cloudflare.com/cache/advanced-configuration/vary-for-images/). Those options remain useful, but they either give up caching, duplicate application logic, need to write additional code, or address a narrower use case. [**`Vary in Cache Rules`**](https://developers.cloudflare.com/cache/how-to/cache-rules/settings/#vary) may fill the gap between these existing features by splitting support into two decisions: `Vary` to identify the request headers that may affect a response. A [Cache Rule](https://developers.cloudflare.com/cache/how-to/cache-rules/) does not force every response to `vary`. If the origin does not return `Vary`, Cloudflare caches the response normally, though the rule may still rewrite `Accept` and `Accept-Language` before forwarding the request to the origin. When the origin does return Vary, Cloudflare uses the configured action for each header it names.  Headers without an individual setting use the rule’s [default action](https://developers.cloudflare.com/cache/how-to/cache-rules/settings/#vary). The three available actions are: | **Action** | **What Cloudflare does** | **Best used for** | | `normalize` | Normalizes request headers before selecting a cached variant, helping equivalent requests share a cached response. Applies header-specific rules to `Accept`, `Accept-Language`, and `Accept-Encoding`. For other headers, it trims optional whitespace and combines repeated header lines in their original order, preserving casing and interior whitespace. | The recommended starting point for negotiation headers where many request values map to a small set of responses. | | `passthrough` | Uses the request header’s raw bytes for cache matching, preserving casing, whitespace, order, and duplicate values. If the header appears on multiple lines, Cloudflare combines those lines in order using commas for cache matching. Passthrough leaves the outgoing header lines unchanged. Cloudflare can still rewrite Accept-Encoding when [Respect Strong ETags](https://developers.cloudflare.com/cache/how-to/cache-rules/examples/respect-strong-etags/) is disabled. | Headers with a controlled set of values, where the exact value changes the response. | | `bypass` | Does not store the response when the origin names that header in `Vary`. Existing cache entries are not removed, so purge them if they need to be cleared. | Use for personalized, high-cardinality, or unexpected headers such as `Cookie` or `User-Agent`. | We recommend **normalize** as the default. For individual headers with personal or unbounded values, use **bypass**. Use **passthrough** when the exact value changes the response. For example, **passthrough** preserves distinctions in casing, whitespace, ordering, and duplicate values, even when the origin treats them as equivalent. With `Vary: X-View` and **passthrough**, these three values produce separate cache keys: `X-View: compact,full` `X-View: Compact,full` `X-View: compact, full` Enough incidental variation can turn a reusable response into many one-off variants in your cache. Regardless of the configured actions, `Vary: *` always bypasses cache. It means any aspect of the request, even information outside the HTTP message (like the client’s IP address), may affect which response the origin selects. Cloudflare therefore cannot reuse the response for a later request without contacting the origin. Let’s follow one of the `/catalog` requests from above through Cloudflare. On the first request, Cloudflare has no stored `Vary` data for the resource, so the cache lookup misses. The matching Cache Rule can normalize configured fields before Cloudflare contacts the origin. This can happen before Cloudflare knows whether the eventual response will contain `Vary`. The Cache Rule defines the permitted normalization; the response later determines whether those fields become part of the cached variant. That ordering matters. If Cloudflare grouped several raw values under one normalized cache key, but the origin still received those raw values, the origin could produce different responses that the cache would later consider interchangeable. Forwarding the normalized value keeps origin selection aligned with cache matching. The origin responds with: `Vary: Accept, Accept-Language` Cloudflare records those header names and stores the response as a cached variant. The header values, processed according to the Cache Rule, distinguish this variant from others for the same resource. When another request for `/catalog` arrives, Cloudflare starts with the resource’s [base cache key](https://developers.cloudflare.com/cache/how-to/cache-keys/): generally the URL plus any other configured key fields. It then reads the stored `Vary` fields and applies the Cache Rule to those headers in the new request to identify the matching cached variant. Suppose they normalize to: `Accept: text/html` `Accept-Language: en,fr` Cloudflare uses those values to look up the matching cached variant directly. It does not compare the request against every stored variant one by one. If a matching variant exists and is fresh, the request is a cache hit. If not, Cloudflare sends the request to the origin and may store the resulting response as another variant. The origin response closes the loop. For each header named in `Vary`, Cloudflare uses the action configured for that header, or the rule’s default action if the header is not listed individually: `Vary: *`, Cloudflare does not store it. This places an important responsibility on the origin. Every cacheable response that can differ based on request fields must return the appropriate `Vary` header consistently, including errors and fallback responses. If one response omits it, Cloudflare could cache that response without the variance needed to keep it isolated. The cache keys in the diagram are conceptual. The later request assumes a fresh cached response. Any purge targeting a cached resource covers all its Vary variants. Existing [requirements](https://developers.cloudflare.com/api/resources/cache/methods/purge/) for purging custom cache keys still apply. Changing a `Vary` configuration does not automatically purge existing content. The new policy may produce different cache keys: requests can miss and refill under the new keys, while old entries remain until they expire or are purged. Remember the requests from above asking for English and French? `Accept-Language: en-US, fr;q=0.8` `Accept-Language: fr;q=0.8, en-GB` Both requests prefer English, but **passthrough** would treat them as different variants. If the Cache Rule allows `en`, `fr`, and `de`, **normalize** reduces both to `en`,`fr`, allowing them to share a cached response. To do this, Cloudflare lowercases values in `Accept`, `Accept-Language`, and `Accept-Encoding`, then sorts them by [quality value](https://developer.mozilla.org/en-US/docs/Glossary/Quality_values), the highest first, with alphabetical ordering to break ties. The client’s ordering therefore does not affect the cache key. After sorting, Cloudflare strips parameters from entries with a nonzero quality value. It can also lose `q=0` (“not acceptable”) when shortening language tags or filtering to the configured formats and languages. For example, `en-US;q=0` can become en. Use passthrough for `Accept` or `Accept-Language` if the origin needs to see those exclusions. You can also configure the rule to keep only specified media types or languages in `Accept` and `Accept-Language`. Regional [language tags](https://developer.mozilla.org/en-US/docs/Glossary/BCP_47_language_tag) such as `en-US` reduce to their base language, en, unless the full tag is configured. This lets you align **normalization** with the formats and languages your origin actually serves. To keep origin selection aligned with cache matching, Cloudflare forwards the normalized `Accept` and `Accept-Language` values to the origin. It also forwards **normalized** `Accept-Encoding` values when [`Respect Strong ETags`](https://developers.cloudflare.com/cache/how-to/cache-rules/examples/respect-strong-etags/) is enabled. Other headers are normalized only for cache matching. In the Cloudflare dashboard, go to **Caching > [Cache Rules](https://dash.cloudflare.com/?to=/:account/:zone/caching/rules/cache/new)**, create or edit a rule, make the response eligible for cache, and add the **Vary** setting. Set the default behavior, then add the headers your origin is expected to name. The same configuration is available through the Rulesets API in the `http_request_cache_settings` phase. The default setting chooses a fallback action for headers your origin names in `Vary` that you have not configured individually. This example normalizes `Accept` and `Accept-Language` to a configured set of formats and languages. The default normalize action also applies to other headers named in `Vary`: This is a complete request body for a `PUT` to the `http_request_cache_settings` phase entrypoint. A `PUT` replaces every rule in that entrypoint. If you already have Cache Rules, include them in the rules array or use the appropriate single-rule create or update operation instead. If the origin serves one representation for each media type and language pair, there are six content combinations. That does not cap the cache at six keys. Preference order, missing headers, and values that normalize to empty can create more. Keep the supported set small and define the rule’s boundaries clearly. After rollout, test the same URL with different header values that should normalize to the same cached variant. Send the test requests from the same client, confirm they return the expected format and language, and inspect [CF-Cache-Status](https://developers.cloudflare.com/cache/concepts/cache-responses/). Look for hits once the cache is populated, and investigate persistent miss responses or unexpected bypass responses. For limitations, additional examples, and how to set this in Terraform, see the [Vary documentation](https://developers.cloudflare.com/cache/how-to/cache-rules/settings/#vary). At this point, an obvious question is, “why not add `Accept` and `Accept-Language` to a [custom cache key](https://developers.cloudflare.com/cache/how-to/cache-keys/#create-custom-cache-keys)?” That works when those fields are always part of the resource’s identity. But a custom cache key adds the configured dimensions to every response covered by the rule, whether the origin used them or not. `Vary` is response-driven, but cacheable responses under the same base key need a consistent set of `Vary` fields. Use a custom cache key when a request property always defines the resource. Use `Vary` when the origin declares the same set of request fields across cacheable responses. Avoid placing the same header in both unless the duplication is deliberate and tested. `Vary` helps solve an obvious problem: one URL can have more than one correct response. But it hands a cache a harder problem, which request differences actually matter? The origin knows which responses it can serve. The cache needs to know which requests can reuse each response. `Vary` in Cache Rules connects those two views. The origin identifies the request fields that may affect a response. You decide whether to **normalize** values, use **passthrough** for exact differences, or keep the response out of cache. `Vary` was never too ugly to be useful. But configuring supported formats and languages manually may not suit every application. We’re evaluating whether ideas from [the expired Availability Hints draft](https://datatracker.ietf.org/doc/draft-nottingham-http-availability-hints/) could reduce that work by letting origins describe the representations they serve directly. `Vary` in [Cache Rules](https://developers.cloudflare.com/cache/how-to/cache-rules/settings/#vary) is available today on Free, Pro, Business, and Enterprise plans through the [Cloudflare dashboard](https://dash.cloudflare.com/?to=/:account/:zone/caching/rules/cache/new), [Rulesets API](https://developers.cloudflare.com/cache/how-to/cache-rules/create-api/), and [Terraform](https://developers.cloudflare.com/cache/how-to/cache-rules/terraform-example/).