No-Vary-Search

Cache efficiency suffers when tracking parameters create separate entries for identical content. The No-Vary-Search response header declares which URL query parameters are irrelevant for cache matching, allowing caches and browsers to reuse responses across URLs differing only in ignored parameters.

Usage

By default, HTTP caches treat URLs with different query strings as distinct resources. A response cached for /results?q=shoes&utm_source=email is not reused for /results?q=shoes&utm_source=social, even when the server returns identical content for both. The No-Vary-Search header changes this behavior by telling caches which query parameters to ignore during matching.

The header uses Structured Fields syntax. Three directives control matching: key-order ignores parameter ordering, params lists parameters to ignore (or ignores all parameters when used as a boolean), and except lists parameters still affecting matching while ignoring everything else.

The most common use case is analytics and tracking parameters. Parameters like utm_source, utm_medium, utm_campaign, fbclid, and gclid do not change the server response but create separate cache entries for every combination. Click identifiers such as gclid and fbclid carry a unique value for every ad click, so each visit from a campaign lands on a new cache entry used once. Declaring these in No-Vary-Search allows caches to serve a single response regardless of tracking parameter variations.

No-Vary-Search covers the URL, while Vary covers request headers. The two work together: a response matched through No-Vary-Search still needs the request headers listed in Vary to match before reuse.

The header integrates with the Speculation Rules API for prefetch and prerender matching. When a page is prefetched with one set of query parameters and the user navigates with a different set, the browser checks No-Vary-Search to determine whether the prefetched response is reusable. Speculation rules support an expects_no_vary_search field hinting at the expected header value before the prefetch response arrives, allowing the browser to decide whether to wait for an in-flight prefetch or start a new request.

How caches match responses

A cache first looks for a stored response under the exact request URL. When no exact match exists, the cache applies the No-Vary-Search rule from the most recent response stored for the same URL path: the ignored parameters are removed, and with key-order the remaining parameters are sorted by name. The cache then looks up the simplified URL and reuses any stored response matching the simplified form.

The rule arrives on the response, so a cache only learns which parameters to ignore after the origin answers once. The first request for each path always goes to the origin. Changing the header value on the origin applies to responses stored from then on, and older entries follow the rule stored with the most recent response for the path.

Every response for the same path needs the same No-Vary-Search value, including error pages and redirects. A response sent without the header resets the cache to the default behavior for the path, where every parameter counts.

Query string comparison

Caches parse the query string the same way HTML forms encode data before comparing parameters. Parsing decodes percent-encoding, so ?a=%61 and ?a=a hold the same value. A space written as %20, as +, or as a literal space character decodes to the same value.

Parsing performs no Unicode normalization. The word "café" written with a precomposed é (caf%C3%A9) and written as e plus a combining accent (cafe%CC%81) stay different values and different cache entries. Generating links from one code path keeps the encoding consistent.

A header failing to parse falls back to the default behavior, where every parameter affects matching. The failure is silent: pages load normally, and only the cache hit rate shows the rule is not applied. Request headers listed in Vary follow comparable normalization rules, reducing equivalent values to one cache entry.

Shared caches and safety

Browser caches hold responses for one user, so an over-broad rule mostly causes stale or mismatched pages for the same visitor. Shared caches such as CDNs and forward proxies serve many users, and an over-broad rule there serves one user's response to another. A parameter marked as ignorable while the server reads the parameter to build the response leaks personalized content across users.

Keep every parameter the server processes out of the ignore list. Session tokens, API keys, signed URL signatures and expiry values, and parameters selecting language, currency, or pagination all belong in the cache key. Only list parameters the server never reads, such as analytics tags consumed by client-side scripts.

Directives

key-order

The key-order directive tells caches to ignore the order of query parameters. URLs with the same parameters in a different sequence match the same cached response. Parameter names and values must still match.

params

The params directive controls which parameters are ignored during cache matching. Used as a list (params=("utm_source" "utm_medium")), only the listed parameters are ignored while other parameters still affect matching. Chromium additionally accepts a boolean form (params) ignoring all query parameters so the cache matches on the URL path alone, a form the draft rejects.

except

The except directive lists parameters remaining significant for cache matching. All other parameters are ignored. This is the inverse approach: instead of listing parameters to ignore, list only the parameters mattering for cache. The draft defines except as standalone and forbids combining except with params in any form, while Chromium requires the boolean params directive alongside except.

Example

Ignoring analytics parameters. Responses are reused across different UTM tracking combinations, improving cache hit rates for marketing campaigns.

No-Vary-Search: params=("utm_source" "utm_medium" "utm_campaign" "utm_content" "utm_term")

Ignoring click identifiers from ad platforms, where every click carries a unique value.

No-Vary-Search: params=("gclid" "fbclid" "msclkid")

Ignoring parameter order. The cache treats ?a=1&b=2&c=3 and ?b=2&a=1&c=3 as the same resource.

No-Vary-Search: key-order

Ignoring all parameters except a product identifier. Only the id parameter affects which cached response is returned. All other parameters (sorting, filtering, tracking) are irrelevant.

No-Vary-Search: params, except=("id")

The draft's form of the same policy names the significant parameter with a standalone except list, the shape the specification validates.

No-Vary-Search: except=("id")

Ignoring all query parameters entirely. The cache matches on the URL path alone, treating /page?anything=here the same as /page.

No-Vary-Search: params

Combining key-order with an except list. Parameter order is ignored, and only the q search term affects cache matching.

No-Vary-Search: key-order, params, except=("q")

A speculation rules script using expects_no_vary_search to indicate prefetched pages ignore tracking parameters. The browser reuses a prefetch even when the navigation URL has different UTM values.

{
  "prefetch": [{
    "source": "list",
    "urls": ["/results?q=shoes"],
    "expects_no_vary_search":
      "params=(\"utm_source\" \"utm_medium\")"
  }]
}

Troubleshooting

  1. Header present but no cache reuse. The header failed to parse, or the browser dropped a directive, and the cache fell back to the default behavior. Common causes are combining params with except in a cache following the draft, a standalone except in Chromium, a boolean params outside Chromium, or parameter names written as bare tokens instead of quoted strings. Check the value with curl -sI "https://www.example.re/results?q=shoes" | grep -i no-vary-search and compare the syntax with the list form params=("utm_source").

  2. Wrong content served for a URL. A parameter listed as ignorable changes the response. A visitor requesting ?lang=fr receives the cached English page because lang sits in the ignore list, or an except list omits lang. Remove the parameter from params, or add the parameter to except, then purge the affected URLs.

  3. Rule applied inconsistently. Some responses for the path omit the header, often redirects, error pages, or responses from a different backend. The most recent response without the header resets matching to the default. Send the same value on every response for the path.

  4. Prefetch not reused on navigation. The navigation URL differs from the prefetched URL in a parameter the header does not ignore, or the expects_no_vary_search hint differs from the header the server returns. Chrome DevTools lists each prefetch and the prefetch status in the Application panel under Speculative loads. Align the hint with the real header value.

  5. CDN ignoring the header. Support in shared caches is limited, and many CDNs treat the full query string as part of the cache key regardless of No-Vary-Search. CDNs offer the same control through cache key settings for query strings, usually an ignore list or an allow list of parameters. Configure the CDN rule to match the header so browser and CDN caches agree.

  6. Same value in different encodings. Links built by different systems encode the same text differently, for example precomposed and decomposed accented characters. Percent-encoding and + for spaces decode to the same value, but Unicode forms stay distinct. Normalize link generation to one Unicode form.

See also

Last updated: September 30, 2026