How must-revalidate Interacts with max-age
max-age sets how long a response is considered fresh, but it says nothing about what a cache is allowed to do once that window closes. Left alone, most caches treat expiry as a soft boundary: they attempt revalidation, but if the origin is slow or unreachable, they often fall back to serving the stale copy rather than failing the request. Adding must-revalidate removes that fallback entirely. It does not change when a response becomes stale — it changes what happens the instant it does, converting a soft, forgiving expiry into a hard one.
Prerequisite Concepts
Before working through the interaction, make sure you’re familiar with the surrounding directives:
- What happens when
max-ageexpires — the baseline revalidation flow thatmust-revalidatemodifies. - Header stacking and directive precedence — how multiple directives on one response resolve when their effects overlap.
- Serving stale content with
stale-while-revalidate— the grace-period mechanism thatmust-revalidateexplicitly overrides.
Step-by-Step Resolution
Step 1 — Serve a baseline response with max-age only
Configure the origin to return a short freshness lifetime with no revalidation directive:
HTTP/1.1 200 OK
Cache-Control: max-age=5
ETag: "v1-abc123"
Date: Mon, 06 Jul 2026 12:00:00 GMT
Request it once to populate the cache, then wait past the 5-second window:
curl -sI https://example.com/report.json | grep -iE '(cache-control|age|date)'
sleep 6
At this point the stored copy is formally stale, but nothing has been discarded — it’s still sitting in the cache, and RFC 9111 §4.2.4 permits (without prohibiting) a shared cache to keep it around and consider serving it if revalidation cannot complete.
Step 2 — Simulate an origin outage on the stale response
With the response now stale, force the origin into a failure state — stop the upstream process, or point the CDN at an unreachable backend — and repeat the request through the shared cache:
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/report.json
Many production caches (and CDNs configured with a “serve stale on error” policy) return the old 200 OK body with Age continuing to climb, rather than propagating the origin failure to the client. This is the default, forgiving behavior that plain max-age allows but does not mandate — it’s implementation-dependent, which is exactly the ambiguity must-revalidate closes.
Step 3 — Add must-revalidate and repeat the outage test
Change the origin’s response header to:
HTTP/1.1 200 OK
Cache-Control: max-age=5, must-revalidate
ETag: "v1-abc123"
Date: Mon, 06 Jul 2026 12:00:10 GMT
Repeat steps 1 and 2 exactly. Populate the cache, wait past the 5-second window, then take the origin offline and request again:
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/report.json
This time the cache must not return the stale body. Per RFC 9111 §5.2.2.2, a stale response marked must-revalidate must not be used to satisfy a subsequent request without successful validation from the origin. Since validation cannot succeed, the correct response is an error — typically surfaced by a reverse proxy or CDN as:
HTTP/1.1 504 Gateway Timeout
Content-Type: text/plain
The stale-serving fallback that step 2 demonstrated is gone. The client sees the failure instead of quietly outdated data.
Step 4 — Confirm must-revalidate overrides stale-while-revalidate
A common assumption is that stacking stale-while-revalidate alongside must-revalidate will still grant a grace window. Test it:
HTTP/1.1 200 OK
Cache-Control: max-age=5, must-revalidate, stale-while-revalidate=30
ETag: "v1-abc123"
Repeat the same expire-then-fail sequence from Step 3. Even though stale-while-revalidate=30 nominally permits serving stale content for 30 seconds after expiry while a background revalidation runs, RFC 9111 is explicit that a cache must not apply stale-while-revalidate to a response carrying must-revalidate. The hard-failure behavior from Step 3 persists — the 504 still appears, confirming must-revalidate wins the conflict.
Expected Output / Verification
- Before
must-revalidate, a stale response under origin failure returns200 OKwith an incrementingAgeand an unchangedETag— the cache served its last-known-good copy. - After adding
must-revalidate, the same failure sequence returns a504 Gateway Timeout(or an equivalent 5xx from your specific proxy/CDN) instead of the stale body. - Adding
stale-while-revalidatealongsidemust-revalidateproduces no observable change from Step 3 — the grace window is suppressed. If your cache instead serves stale content in this configuration, that cache is not RFC 9111-compliant for this directive combination. - Within the freshness window (before
max-ageelapses), all three configurations behave identically —must-revalidatehas no effect on fresh responses.
Edge Cases
- Browser caches vs shared caches —
must-revalidateapplies to private (browser) caches too, unlikeproxy-revalidate, which only binds shared caches. A browser tab reloading a stale,must-revalidate-tagged resource over a flaky connection can surface a hard network error rather than a stale render. - HTTP/1.1 vs HTTP/2 — the revalidation semantics are identical across protocol versions; only the transport for the conditional request changes.
- Missing validators — if the response has no
ETagorLast-Modified, the cache cannot issue a conditional request at all. Withmust-revalidatepresent and validators absent, an origin outage guarantees an error response, since a full unconditional re-fetch is the only remaining option and it will also fail. - Interaction with
no-cache—no-cacheforces revalidation on every request regardless of freshness, makingmust-revalidateredundant on responses that already carry it; the two are commonly confused but solve different problems.
Frequently Asked Questions
Does must-revalidate do anything before max-age expires?
No. It is inert while the response is fresh. It only activates once the freshness lifetime set by max-age (or s-maxage) has elapsed and the stored response becomes stale.
Does must-revalidate override stale-while-revalidate?
Yes. RFC 9111 states a cache must not apply the stale-while-revalidate grace window to a response that also carries must-revalidate. The hard-revalidation rule takes precedence.
What does a cache do if the origin is unreachable during revalidation?
Without must-revalidate, a cache is permitted to serve the stale response rather than fail. With must-revalidate present, the cache must not serve the stale copy and instead returns an error, typically a 504 Gateway Timeout.
Is must-revalidate the same as proxy-revalidate?
They enforce the same rule on different cache scopes. must-revalidate applies to all caches, including private browser caches. proxy-revalidate applies only to shared caches, leaving private caches free to serve stale.
Related
- Public vs private cache scope — explains why
must-revalidatebinds both cache tiers whileproxy-revalidatebinds only shared ones. - Cache-Control best practices for REST APIs — covers when forcing a hard failure over stale data is the correct choice for API responses.
- Conditional requests and 304 responses — the revalidation mechanism that
must-revalidatemakes mandatory once a response goes stale.