HTTP caching
There is one rule underneath all of it. A stored response may be reused without asking anyone if, and only if:
freshness lifetime > current age ^ ^ what the server said how long since it was generated (s-maxage, then max-age, (the Age field, plus local time then Expires) since it arrived)Everything else — every directive, every field — is a way of setting one of those two terms. Get that formula and the rest of RFC 9111 stops being a list of magic words.
Fresh, stale, and the 304
A fresh entry is used with no network activity at all. Not a faster request — no request. Nothing is sent, nothing is waited for, and the server never learns the page was viewed.
A stale entry is not useless. The client still holds the body; what it has lost is the right to assume it is current. So it asks a conditional question using a
validator it was given earlier, and a 304 Not Modified says "still good,
keep what you have". The round trip is spent; the body is saved.
| Directive | Says |
|---|---|
max-age=N | Fresh for N seconds, in any cache |
s-maxage=N | …but N for shared caches specifically |
no-cache | Store it, but always revalidate before use |
no-store | Do not write it down at all |
private | Browsers may store it; shared caches may not |
immutable | Do not even revalidate on a reload |
no-cache and no-store are the pair people reach for interchangeably and should not:
the first still caches, it just always asks first.
Two validators, and who the cache is for
An ETag is an opaque version marker — the strong validator, since only the
server knows what it means. Last-Modified is a timestamp with one-second resolution,
which is a real weakness for anything that changes more often than that.
The other half of the design is whose cache. A browser cache holds one user's
responses; a shared cache — a CDN edge, a reverse proxy — holds everybody's. That is why
private exists, and why marking a personalised page publicly cacheable is one of the
more spectacular ways to leak data.
Key takeaways
- One rule underlies all of it: reuse without asking is allowed while freshness lifetime exceeds age.
- A fresh hit is no request at all. A 304 is a real round trip that saves only the body — still usually worth it.
- no-cache stores and always revalidates; no-store refuses to store. They are not interchangeable.
- ETags are strong validators because only the server knows what they mean; Last-Modified has one-second resolution.
- Shared caches hold everybody’s responses, so anything personalised needs `private` — its absence leaks silently and produces no error.