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
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
paramswithexceptin a cache following the draft, a standaloneexceptin Chromium, a booleanparamsoutside Chromium, or parameter names written as bare tokens instead of quoted strings. Check the value withcurl -sI "https://www.example.re/results?q=shoes" | grep -i no-vary-searchand compare the syntax with the list formparams=("utm_source").Wrong content served for a URL. A parameter listed as ignorable changes the response. A visitor requesting
?lang=frreceives the cached English page becauselangsits in the ignore list, or anexceptlist omitslang. Remove the parameter fromparams, or add the parameter toexcept, then purge the affected URLs.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.
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_searchhint 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.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.
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
- The No-Vary-Search HTTP Caching Extension (IETF Draft)
- Speculation Rules API (MDN)
- Speculation Rules
- Sec-Purpose
- Vary
- Cache-Control
- Structured Fields
- Caching
- HTTP headers