HTTP Caching Table

DirectiveWhat it tells the cacheField note
max-age=3600Fresh for 3600 secondsThe core freshness knob; 0 means revalidate every time
no-cacheStore, but revalidate before useNOT no-store - the most misread header on the web
no-storeNever store anythingFor truly private responses; kills all performance gains
publicShared caches may store itImplied for most responses; matters behind auth
privateBrowser-only cacheCDNs and proxies must not store
s-maxage=600CDN freshness overrideShared caches obey it over max-age - tiered freshness
stale-while-revalidate=60Serve stale, refresh in backgroundThe instant-then-fresh pattern for shell assets
must-revalidateNo stale serving after expiryPrevents caches guessing once freshness lapses
ETag / If-None-MatchValidator for 304 repliesFingerprint; match means the body need not resend
Last-Modified / If-Modified-SinceDate-based validatorWeaker than ETag - second resolution, clock-trusting
Reference: the MDN HTTP caching guide. The model in one line: a response carries freshness (max-age) and validators (ETag/Last-Modified) - while fresh, caches serve without asking; once stale, they revalidate and may get a body-less 304. The two header typos that break sites: no-cache (revalidate, still cacheable) confused with no-store (never cache), and forgetting that HTML itself is cacheable - the default no Cache-Control header at all is why staging changes 'do not show up'. Bottom line: fingerprinted assets get max-age=31536000, immutable; HTML gets no-cache so the freshest asset URLs are always discovered. Related tools: HTTP headers table and status codes table for the 304 dance, and web vitals table for why repeat views feel instant.

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

  1. Classify the response first: fingerprinted asset, public HTML, or private data - each maps to a row in the table.
  2. Set freshness with max-age (browser) and s-maxage (CDN tier); add stale-while-revalidate where instant-then-fresh is acceptable.
  3. 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.

Related tools