The response cannot be revalidated without downloading it again
What this check looks for
Your response carries no fingerprint, so a browser holding an old copy has no way to ask 'has this changed?'. It has to download the whole thing to find out.
Why it matters
Every repeat visit re-downloads bytes the visitor already has. On a slow connection that is the difference between a page that appears instantly and one that reloads from scratch, and it is bandwidth you pay for on both ends.
When the check passes, your report says: “The response carries a validator, so it can be revalidated cheaply”.
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.caching family ceiling of 12 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.caching · 12
- Category
- HTTP
- Module
- Http headers
- Fix owned by
- user
- In the ruleset since
- 2026.09
How to fix it
Send an ETag or a Last-Modified date so caches can revalidate cheaply.
Without one, every freshness check costs a full download instead of a 304.
For static files, confirm the server has not been configured to strip them — nginx sends
ETagandLast-Modifiedby default (etag on;), and Apache sends both for files on disk.For a dynamic response, compute a strong
ETagfrom a hash of the body, or a weak one from the underlying record's version or updated-at timestamp.Handle the conditional request as well as emitting the validator: an
ETagnobody checks againstIf-None-Matchsaves nothing.Pair it with
Cache-Control: no-cacheon documents — that combination is what makes revalidation both mandatory and cheap.
How to confirm it worked
curl -sSI https://‹host›/ | grep -iE '^(etag|last-modified):'
curl -sSI -H 'If-None-Match: "<the etag you just saw>"' https://‹host›/ — expect 304 Not Modified
Cache-Control: no-cache
ETag: "a1b2c3d4"A 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
location ~* "\.[0-9a-f]{8,}\.(?:css|js|woff2|png|jpg|svg)$" {
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
location / {
add_header Cache-Control "no-cache" always;
}<IfModule mod_headers.c>
<FilesMatch "\.[0-9a-f]{8,}\.(css|js|woff2|png|jpg|svg)$">
Header always set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.(html|htm)$">
Header always set Cache-Control "no-cache"
</FilesMatch>
</IfModule>@immutable path_regexp \.[0-9a-f]{8,}\.(css|js|woff2|png|jpg|svg)$
header @immutable Cache-Control "public, max-age=31536000, immutable"
header Cache-Control "no-cache"Cache Rules → Create rule → set Browser TTL and Edge TTL per path, or Rules → Overview → Create rule → Response Header Transform Rule → Set static → Cache-Control. Dashboard paths as at 2026-09.Two rules, not one blended value. Note that an
add_headerinside alocationblock discards everyadd_headerinherited from the enclosing server block, so each location must repeat the headers it needs.immutableis only safe for a URL that changes whenever its content does.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. It also needs mod_headers loaded; the IfModule guard means the configuration still parses when it is not, but the header is then simply absent.
Caddy applies the more specific matcher first; the unmatched rule is the fallback.
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. A Cache Rule also changes what Cloudflare itself stores, which a Transform Rule does not — decide which of the two you actually mean.
Technical detail
Neither ETag nor Last-Modified was present on the response from ‹origin›. ‹detail›
A validator is what turns a cache check into a 304 Not Modified: the client sends If-None-Match with the entity tag, or If-Modified-Since with the date, and a server that recognises it answers with headers and no body. Without one, no-cache — which mandates revalidation — degrades into a full re-download every single time, which is the opposite of what it was set for.
Most servers do this for static files automatically; a response missing both validators usually comes from an application that builds it dynamically. Note that on-the-fly compression can alter or weaken the entity tag, and that a weak validator (W/"…") still supports revalidation perfectly well — it merely cannot be used for byte-range requests. Detected server: ‹detected server›.
Standards and references
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
- A compressed response does not vary on Accept-Encoding
- Vary: * makes the response uncacheable by any shared cache