Vary: * makes the response uncacheable by any shared cache
What this check looks for
This response tells every shared cache and CDN never to reuse it. If you are paying for a CDN in front of this site, this header is switching it off.
Why it matters
Every request travels all the way to your origin, so you carry the full load and visitors far away get the full round trip. The CDN still appears in your bill and in your architecture diagram while doing nothing.
When the check passes, your report says: “Vary lets shared caches and CDNs reuse the response”.
What it costs your score
When this check fails it removes 4 points from your HTTP score, before the status, confidence and repeat multipliers are applied. HTTP carries a weight of 7 in the overall score.
It shares the http.compression family ceiling of 18 points: however many findings that family produces, together they cannot remove more than that from HTTP. One underlying problem showing up in several places is still one problem.
- Severity
- low
- Default confidence
- confirmed
- Status when triggered
- warn
- Deduction
- 4 points
- Family cap
- http.compression · 18
- Category
- HTTP
- Module
- Http headers
- Fix owned by
- user
- In the ruleset since
- 2026.09
How to fix it
Replace Vary: * with the request headers the response genuinely depends on.
As written it disables every shared cache and CDN in front of the site.
List the fields that actually select the representation, typically
Accept-Encoding, andAccept-Languageif you serve translations.If the intent was 'do not cache this per-user response', say that instead:
Cache-Control: private, orno-storefor something genuinely sensitive.Keep the
Varylist short — every field named multiplies the number of copies a cache must store, and aVary: User-Agentis close to*in effect.
How to confirm it worked
curl -sSI https://‹host›/ | grep -i '^vary:' — expect a named list, not *
curl -sSI https://‹host›/ twice and check the CDN's own cache-status header reports a hit on the second request
Vary: Accept-Encoding
Cache-Control: private, no-cacheA named slot like ‹domain› — and the braces left in the configuration below — is filled in with your own values when this rule appears on a report.
Remediation by platform
gzip on;
gzip_vary on;
gzip_min_length 256;
gzip_comp_level 5;
gzip_types text/plain text/css text/xml application/javascript application/json image/svg+xml;
# Brotli, if the ngx_brotli module is available:
# brotli on;
# brotli_types text/plain text/css text/xml application/javascript application/json image/svg+xml;<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/css text/xml application/javascript application/json image/svg+xml
</IfModule>
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS text/html text/css application/javascript application/json
</IfModule>encode zstd gzipSpeed → Optimization → Content Optimization, or Rules → Overview → Create rule → Compression Rule to choose the algorithm per path. Dashboard paths as at 2026-09.text/htmlis always compressed by nginx and must not be listed ingzip_types— listing it logs a duplicate-MIME warning. Brotli is not part of a stock nginx build:brotli on;fails to start a server without the ngx_brotli module compiled in, so leave those lines commented untilnginx -Vshows it.Shown because the response identified Apache. Put it in the virtual host or server configuration rather than .htaccess: .htaccess is re-read on every request, is often disabled by AllowOverride None, and runs too late to influence a response the virtual host generates itself. mod_deflate adds
Vary: Accept-Encodingon its own. mod_brotli ships with Apache 2.4.26 and later but is not enabled by default —a2enmod brotli, or the equivalent LoadModule line, is required, and the IfModule guard keeps the configuration valid until then.Caddy's
encodedirective negotiates the encoding and setsVary: Accept-Encodingitself. A stock Caddy build offers gzip and zstd; Brotli is not one of its on-the-fly encoders, so do not writeencode brand expect it to work.This applies only to responses that reach visitors through Cloudflare. An origin that is reachable directly — by IP, or through a DNS record that is not proxied — still serves the response measured here unchanged. Setting it at the origin as well covers both paths. Cloudflare compresses eligible responses by default, so a response measured as uncompressed here usually means the scan reached the origin directly.
Technical detail
The response from ‹origin› carries Vary: *. ‹detail›
RFC 9110 §12.5.5 states that a Vary field value of * signals that the response varies on something not captured in the request headers at all, and RFC 9111 requires a cache to treat such a response as unable to match any subsequent request. The effect is total: no shared cache will ever reuse it, whatever Cache-Control says alongside.
It is usually set defensively — 'this response is personalised, do not cache it' — by someone reaching for the wrong header. The correct expression of that intent is Cache-Control: private (a browser may store it, a shared cache may not) or Cache-Control: no-store (nobody stores it at all). Both say what is meant, and neither destroys caching for the responses that are not personalised. Detected server: ‹detected server›.
Standards and references
- RFC 9110 §12.5.5 — Vary § 12.5.5
- MDN — Vary
Test this on your domain
Run the check that produces this finding, on its own, against any domain.
Other http headers checks
- The caching instructions contradict each other
- A text response does not declare its character encoding
- The declared Content-Encoding may not match the body
- A compressible response was sent uncompressed
- The response has a body but no Content-Type
- The response sends obsolete headers, one of them harmful
- No Alt-Svc header advertises HTTP/3
- A response header leaks an internal address
- The response does not say how long it may be cached
- The response cannot be revalidated without downloading it again
- A compressed response does not vary on Accept-Encoding