devkult_
tools29converters34
home/headers/cache-control

Cache-Control

request & response headercaching

Directives that tell browsers and CDNs whether, where and for how long a response may be cached.

Cache-Control is the main caching header. On a response, it says who may store the response and how long it stays fresh before it must be revalidated. On a request, it lets the client ask caches to revalidate or bypass stored copies — which is what browsers send on a hard reload.

Syntax

Cache-Control: <directive>[=<value>], <directive>…

Defined in RFC 9111 §5.2 — Cache-Control.

Values

max-age=<seconds>fresh for this many seconds in any cache
s-maxage=<seconds>freshness for shared caches (CDNs) only; overrides max-age there
publicany cache may store it, even for authenticated requests
privateonly the user's browser may store it, not CDNs
no-cachemay be stored, but must be revalidated before every use
no-storemust not be stored anywhere
must-revalidateonce stale, never serve without revalidating
immutablewill never change — don't revalidate, even on reload
stale-while-revalidate=<seconds>serve a stale copy for this long while refreshing in the background

Examples

Cache-Control: public, max-age=31536000, immutable

fingerprinted static assets such as app.3f2a1c.js

Cache-Control: no-cache

HTML: always revalidate, so deploys show up on the next load

Cache-Control: private, no-store

personal or sensitive data

Cache-Control: public, s-maxage=60, stale-while-revalidate=300

API responses cached briefly on a CDN

Good to know

  • no-cache means 'revalidate before use', not 'don't cache'. Use no-store to keep a response out of caches.
  • Without Cache-Control, browsers guess a freshness lifetime from Last-Modified — always set it explicitly on HTML.
  • s-maxage only affects shared caches; CDNs such as Cloudflare and Vercel honour it.
  • Expires is the HTTP/1.0 predecessor; when both are present, max-age wins.

Further reading: HTTP caching explained

How to set Cache-Control

Express
// Fingerprinted assets: cache for a year
app.use("/assets", express.static("dist/assets", { maxAge: "1y", immutable: true }));

// HTML and API responses: always revalidate
res.set("Cache-Control", "no-cache");
Next.js (route handler)
return Response.json(data, {
  headers: { "Cache-Control": "public, s-maxage=60, stale-while-revalidate=300" },
});
// Next.js already sends long-lived immutable caching for /_next/static files.
nginx
location /assets/ {
  add_header Cache-Control "public, max-age=31536000, immutable" always;
}

location / {
  add_header Cache-Control "no-cache" always;
}
Apache (.htaccess)
<FilesMatch "\.(js|css|woff2|png|jpg|svg)$">
  Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
<FilesMatch "\.html$">
  Header set Cache-Control "no-cache"
</FilesMatch>
fetch (request)
// Ask caches along the way to revalidate
await fetch("/api/data", { headers: { "Cache-Control": "no-cache" } });

// Or control the browser's own HTTP cache for this request
await fetch("/api/data", { cache: "no-store" });

Check it

Fetch only the response headers and filter for Cache-Control. In the browser, it's under Response Headers in DevTools → Network.

curl -sI https://example.com/ | grep -i '^cache-control:'

To compare response headers across many URLs at once, use the HTTP Status Checker.

Frequently asked questions

What's the difference between no-cache and no-store?

no-cache lets caches store the response but makes them check with the server — usually a cheap 304 — before each use. no-store forbids storing it at all.

What's the longest sensible max-age?

One year (31536000 seconds) is the convention for immutable, fingerprinted assets. Anything that can change at the same URL needs a much shorter lifetime, or no-cache with an ETag.

Status codes it appears with
Related headers