9 min
  • HTTP
  • Caching

HTTP caching

There is one rule underneath all of it. A stored response may be reused without asking anyone if, and only if:

text
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.

Loading the HTTP Explorer simulation…

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.

DirectiveSays
max-age=NFresh for N seconds, in any cache
s-maxage=N…but N for shared caches specifically
no-cacheStore it, but always revalidate before use
no-storeDo not write it down at all
privateBrowsers may store it; shared caches may not
immutableDo 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.

Check yourself
A response is marked no-cache. A user views it twice. What happens the second time?

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.

Predict what happens next
A logged-in dashboard is served with Cache-Control: max-age=600 and no other directive. It sits behind a CDN. What happens?

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.

Take it further