HTTP Caching Table
| Directive | What it tells the cache | Field note |
|---|---|---|
max-age=3600 | Fresh for 3600 seconds | The core freshness knob; 0 means revalidate every time |
no-cache | Store, but revalidate before use | NOT no-store - the most misread header on the web |
no-store | Never store anything | For truly private responses; kills all performance gains |
public | Shared caches may store it | Implied for most responses; matters behind auth |
private | Browser-only cache | CDNs and proxies must not store |
s-maxage=600 | CDN freshness override | Shared caches obey it over max-age - tiered freshness |
stale-while-revalidate=60 | Serve stale, refresh in background | The instant-then-fresh pattern for shell assets |
must-revalidate | No stale serving after expiry | Prevents caches guessing once freshness lapses |
ETag / If-None-Match | Validator for 304 replies | Fingerprint; match means the body need not resend |
Last-Modified / If-Modified-Since | Date-based validator | Weaker than ETag - second resolution, clock-trusting |
HTTP caching is a conversation between servers and caches about freshness: responses carry instructions (Cache-Control directives) and fingerprints (ETag, Last-Modified), and caches obey them - serving instantly while fresh, revalidating once stale. The table below is the working ten: the directives and validators that cover every real caching decision.
Bottom line: the production pattern is two-tier. Fingerprinted assets (app.abc123.js) get Cache-Control: max-age=31536000, immutable - cache forever, because the filename changes when content does. HTML gets no-cache - always revalidate, so the page discovers the newest asset URLs the moment they ship. One line each, and staging-to-production staleness bugs disappear.
The honest part: the two most misread words on the web are no-cache and no-store. no-cache means "store it, but check with me before using" - it is the revalidation workhorse. no-store means "never save a byte" - the privacy option for truly personal responses. Confusing them either breaks caching entirely or leaks private pages into shared caches, and both failures look identical from the user's chair.
How to use
- Classify the response first: fingerprinted asset, public HTML, or private data - each maps to a row in the table.
- Set freshness with max-age (browser) and s-maxage (CDN tier); add stale-while-revalidate where instant-then-fresh is acceptable.
- Add validators (ETag preferred) so revalidations can be answered with a body-less 304 instead of a full resend.
Frequently asked questions
What is the difference between no-cache and no-store?
no-cache allows caching but requires revalidation before every use - the cached copy is stored and checked against the server, usually resulting in a fast 304. no-store forbids saving anything at all: every request travels to the origin. Banking pages and personalized health data want no-store; a public homepage wants no-cache. Confusing them either disables caching you wanted or caches something private into a shared proxy - and neither failure announces itself.
What is the difference between max-age and s-maxage?
max-age governs every cache including the browser; s-maxage governs only shared caches (CDNs, proxies) and overrides max-age for them. The tiered pattern: s-maxage=600, max-age=60 lets the CDN serve for ten minutes while browsers revalidate each minute - content freshness at the edge with per-user correctness at the browser. Without s-maxage, a long browser max-age pins stale content no matter what the CDN does.
What does stale-while-revalidate do?
It extends freshness grace: after max-age expires, the cache may serve the stale response immediately while fetching the fresh one in the background. The user gets an instant response; the next visitor gets the updated one. One extra second (stale-while-revalidate=1) is usually invisible and turns hard waits into background refreshes for app shells, CSS and fonts. Browsers and most CDNs support it; where unsupported it is simply ignored.
How do ETag and Last-Modified work together?
They are validators - fingerprints the server attaches so a cache can ask "is this still current?" with If-None-Match (ETag) or If-Modified-Since (date). If unchanged, the server answers 304 Not Modified with no body - the cache keeps serving its copy. ETag is preferred: it is content-derived and exact, while Last-Modified has one-second resolution and trusts server clocks. Send both if you must; servers honor If-None-Match first.