Caching and conditional requests
Cache-Control in plain language, ETags versus Last-Modified, and revalidation with 304 responses.
Controlling freshness
Cache-Control: max-age=3600, public
Cache-Control: no-store # never keep it (sensitive data)
Cache-Control: no-cache # store, but revalidate every time
Cache-Control: private, max-age=600
Cache-Control: immutable, max-age=31536000 # fingerprinted assets| Directive | Effect |
|---|---|
max-age=n | Fresh for n seconds |
s-maxage=n | Same, for shared caches (CDN) only |
public/private | Whether proxies may cache it |
no-cache | Cache it, but always revalidate first |
no-store | Do not store at all |
stale-while-revalidate=n | Serve stale, refresh in the background |
π‘
no-cache does not mean 'do not cache' β that is no-store. The naming has confused people for decades.Conditional requests
Once a response goes stale, the client asks 'has this changed?' rather than downloading it again. Confirmation costs a tiny 304 with no body β the single biggest bandwidth saving available.
# first response
HTTP/1.1 200 OK
ETag: "a1b2c3"
Cache-Control: max-age=0, must-revalidate
# later request
GET /api/profile HTTP/1.1
If-None-Match: "a1b2c3"
# unchanged response - no body transmitted
HTTP/1.1 304 Not Modified| Pair | Validator |
|---|---|
ETag / If-None-Match | Version fingerprint β precise |
Last-Modified / If-Modified-Since | Timestamp β one-second resolution |
A pragmatic setup
- Hashed asset filenames (app.a1b2c3.js) can be cached for a year with
immutable. - HTML should be
no-cacheor short-lived so deploys take effect immediately. - The CDN pattern:
Cache-Control: public, max-age=0, s-maxage=86400, stale-while-revalidate=60. - Never cache personalized responses (
Cache-Control: private) in a shared CDN.
FAQ
Users see an old version after deploy.
Cache-Control on HTML is too long, or a hard refresh is needed. Use fingerprinted asset URLs plus short-lived HTML β that combination invalidation-free.
Does a query string break caching?
It is part of the cache key for most caches. Some CDNs let you ignore selected parameters; do that deliberately, never accidentally.
Related
Last refreshed 2026-09-17.