The header everyone half-remembers
Cache-Control is one of those headers every backend developer has set dozens of times, and almost nobody can recite from memory with full confidence — especially the difference between no-cache and no-store, which sound similar but behave very differently.
no-cache does not mean 'don't cache'
This is the single most common mix-up. no-cache does not prevent caching. It means: cache this response, but revalidate with the origin server before using the cached copy every single time. The browser or CDN still stores the response; it just can't serve it without first checking whether it's still fresh, typically via a conditional request with an ETag or Last-Modified header. If the server responds "still valid," the cached body is used and no re-download happens — you save bandwidth while guaranteeing freshness.
no-store, on the other hand, is the real "don't cache anything, ever" directive. Nothing gets written to any cache, anywhere — not the browser's disk cache, not a CDN edge, not a corporate proxy. This is what you want for genuinely sensitive responses, like anything containing authentication tokens or personal financial data mid-transaction.
public and private control who can cache, not how long
public allows any cache — including shared caches like CDNs and corporate proxies — to store the response, even ones that would normally be considered private by default (like responses with an Authorization header). private restricts caching to the end user's own browser; shared caches must skip it. Neither directive says anything about duration on its own — that's what max-age is for.
max-age vs s-maxage
max-age sets freshness duration for every cache in the chain. s-maxage does the same thing but only for shared caches (CDNs, reverse proxies) and takes priority over max-age for those caches specifically. This lets you set a short max-age for end-user browsers while letting your CDN hold onto the response much longer — a common pattern for content that's fine to be slightly stale at the edge but should refresh quickly in individual browsers.
immutable is underused
Even with a long max-age, browsers sometimes still send a conditional revalidation request when a user hits refresh. The immutable directive tells the browser the response body will never change while it's fresh, so it can skip that check entirely — not just skip re-downloading, but skip asking at all. This is exactly right for files with a content hash baked into the filename (like app.a1b2c3.js), since a changed file would get a new filename and therefore a fresh URL.
stale-while-revalidate softens the cliff edge
Without it, the moment a cached response expires, the very next request has to wait for a full fresh fetch. stale-while-revalidate=N lets a cache serve the stale copy instantly for up to N more seconds after expiry, while fetching an updated copy in the background for the next request — trading a small window of staleness for consistently fast responses.
Getting the combination right
Real headers are combinations of these directives, and it's easy to write one that's technically valid but contradicts itself — like combining no-store with an immutable max-age, which is meaningless since nothing gets stored in the first place. Building the header from a checklist with a live explanation, rather than typing it from memory, is a fast way to catch these contradictions before they ship.
Frequently Asked Questions
no-cache means the response IS cached, but must be revalidated with the server before every use. no-store means the response must never be stored anywhere at all. The tool's explanation panel spells out exactly what your chosen combination does for both browsers and CDNs.
Yes — Cache-Control Header Builder toggles real directives, builds the formatted header string live, and explains what your combination means. It's a one-time $4.99 purchase — no subscription, no account required.
Yes — s-maxage, proxy-revalidate, and stale-while-revalidate are all included alongside the standard directives, since shared caches and CDNs often need different rules than end-user browsers.
Presets include a static asset with a hash in its filename (public, max-age=31536000, immutable), an API response that should always revalidate (no-cache), never-cache responses (no-store), and a short-lived private response for user-specific data.
Yes — if you check no-store alongside directives that only matter for stored responses (like max-age or immutable), a warning explains that those directives become meaningless in that combination.