By · Updated

Cache-Control Header Builder

Max-Age, No-Cache, Immutable and s-maxage Explained for Nginx, Apache and Express

No ratings yet

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