dotvitals

The response cannot be revalidated without downloading it again

LowConfirmedhttp.headers.no-validator

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 the whole score is calculated

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.

  1. For static files, confirm the server has not been configured to strip them — nginx sends ETag and Last-Modified by default (etag on;), and Apache sends both for files on disk.

  2. For a dynamic response, compute a strong ETag from a hash of the body, or a weak one from the underlying record's version or updated-at timestamp.

  3. Handle the conditional request as well as emitting the validator: an ETag nobody checks against If-None-Match saves nothing.

  4. Pair it with Cache-Control: no-cache on 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

The configuration to publish
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

nginx
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;
}
Apache
<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>
Caddy
@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"
Cloudflare
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_header inside a location block discards every add_header inherited from the enclosing server block, so each location must repeat the headers it needs. immutable is 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.

Open the http headers checkerBuild the fix

Other http headers checks