By Tim King · Updated
Cache-Control Header Builder
Max-Age, No-Cache, Immutable and s-maxage Explained for Nginx, Apache and Express
OneTask is free, with no sign-up, and built by one person.
If this tool saved you time, a coffee helps keep new ones coming.
How the Cache-Control Header Builder works
This Cache-Control header builder writes the header for each kind of file. Cache-Control is one line and a dozen words, and getting it wrong either makes every visit
download everything again or leaves users staring at a page you fixed last week. The presets cover
the five cases almost every site has; the custom fields let you build anything else. The output
shows the header, the config line for common servers and, more usefully, what a browser and a
CDN will actually do when they see it.
Cache-Control header builder: the directives that matter
max-age is the number of seconds a response is fresh. While fresh, a browser reuses it without
contacting the server at all. After that it is stale, and what happens next depends on the other
directives. s-maxage is the same thing for shared caches only — CDNs and proxies — and overrides
max-age for them, so a page can be fresh at the edge for ten minutes and in the browser for none.
public and private. private means the response is for one user and shared caches must not
store it; the browser still can. public explicitly allows shared caching even when the request had
an Authorization header, which is otherwise blocked. Leaving both out is fine for most responses:
a max-age on a request without credentials is already cacheable by everyone.
no-cache does not mean do not cache. It means the cache may store the response but has to
revalidate it with the origin before every use. Combined with an ETag that revalidation is a
small request that returns 304 Not Modified and no body, which is how HTML pages and API
responses get fast without going stale. no-store is the one that means what it says: nothing
may keep a copy. Use it for anything sensitive, and nowhere else, because it also disables the
back/forward cache.
immutable tells the browser not to revalidate even when the user presses reload. It only makes
sense on files whose URL changes when the content changes — the hashed bundles a build tool
produces — and with a long max-age. Those two together are the standard pattern for static
assets: cache for a year, never ask again, and let the HTML that references them carry the change.
must-revalidate forbids serving a stale copy under any circumstances, including when the origin is down; by default caches are allowed to serve stale in that case. stale-while-revalidate goes the other way: for the given number of seconds after expiry a cache may serve the stale copy immediately and refresh in the background, so users never wait on the revalidation. stale-if-error allows serving stale when the origin returns a 5xx, which turns a backend outage into nothing visible for cached pages.
The two-header pattern. Almost every site ends up with two policies: long-lived immutable assets under hashed names, and short-lived or revalidated HTML that points at them. The HTML is what carries a deploy to users, so it is the one that must not be cached for long; everything it references can be cached forever.
FAQ
What is the difference between no-cache and max-age=0?
Nearly nothing in practice. max-age=0 makes the copy stale immediately, and a stale copy is
revalidated before reuse in normal cases; no-cache makes that revalidation mandatory in every
case, including back/forward navigation and offline heuristics. Use no-cache when you mean it.
Do I still need Expires or Pragma?
No. Cache-Control overrides Expires in every browser released this century, and Pragma: no-cache is a request header that servers sometimes echo out of habit. Neither does harm; neither
is needed.
Why does my CDN ignore max-age?
Most CDNs have their own edge TTL setting that overrides or supplements origin headers; check for
an "honour origin headers" or "respect existing headers" option. s-maxage is the directive
they should follow, and private or no-store always stops them.
How do I cache an API response?
Usually private, no-cache with an ETag, so each client keeps its copy and asks whether it has
changed. Public, unauthenticated endpoints that change rarely can use public, max-age with
stale-while-revalidate for a fast edge.
What about the Vary header?
Vary tells caches which request headers change the response — Accept-Encoding for compressed
variants, Accept for content negotiation. It is separate from Cache-Control and is worth
setting on anything that serves different bodies for the same URL; Vary: * or Vary: Cookie
effectively disables shared caching.
See also: all Dev Utilities tools, Formatters & Validators, Data & Encoding, Security & Identifiers, SEO, Accessibility, AI.
Related tools
HTTP Header Inspector
Parse pasted HTTP headers, explain each one and audit security.
Resource Hint Generator
Generate correct preconnect, preload and prefetch tags with the right attributes.
Core Web Vitals Calculator
Check whether your page passes Core Web Vitals and which metric to fix first.
Page Weight Calculator
Add up page resources, see the breakdown and load time on each connection.
Gzip / Brotli Size Comparison
Compare what gzip and Brotli save on a real file.