DevToolsmith

Cache-Control Header

The Cache-Control request and response header tells browsers, CDNs and proxies whether a response may be stored, by whom, and for how long.

Type
Request and response header
Category
Caching
Defined in
RFC 9111

Syntax

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

How Cache-Control works

It is the main caching header; when it has max-age, the older Expires header is ignored. The two directives people mix up: no-cache means “store it, but check with the server before every use”, while no-store means “never store it at all”. Requests can send Cache-Control too (a hard reload sends no-cache), but servers and CDNs often ignore it.

Directives

Value Meaning
max-age=<seconds> Fresh for this many seconds, counted from when the response was generated.
s-maxage=<seconds> Overrides max-age for shared caches such as CDNs.
public Shared caches may store it, even for requests with an Authorization header.
private Only the user’s browser may store it, not CDNs or proxies.
no-cache May be stored, but must be revalidated (ETag or Last-Modified) before every use.
no-store Must not be stored anywhere. Use for sensitive responses.
immutable Won’t change while fresh, so the browser doesn’t revalidate even on reload. For hashed file names.
must-revalidate Once stale, never serve it without checking with the server.
stale-while-revalidate=<seconds> After expiry, serve the stale copy for this long while fetching a fresh one in the background.
stale-if-error=<seconds> Serve the stale copy for this long if the server returns an error.

Examples

Hashed static assets (app.3f9a1c.js)
Cache-Control: public, max-age=31536000, immutable
HTML pages: always check for a new version
Cache-Control: no-cache
API response, cached briefly by a CDN
Cache-Control: public, max-age=0, s-maxage=60, stale-while-revalidate=30
Account data: never store
Cache-Control: no-store

Common mistakes

  • Long max-age only works with file names that change on every deploy (hashes); otherwise users keep old files for that long.
  • Without Cache-Control or Expires, browsers still cache responses heuristically, typically for 10% of the time since Last-Modified. Set it explicitly.

Check it

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

Paste a full set of response headers into the HTTP header checker to get every header explained and common mistakes flagged.

Related headers

  • ETag: gives a version identifier to a response, so caches can ask “has it changed?” cheaply.
  • Expires: gives an absolute date after which the response is stale.
  • Vary: lists the request headers that change the response, so caches keep a separate copy for each value.
  • Age: tells how many seconds a response has spent in a cache (usually a CDN) since it was fetched from the origin.

More caching headers

If-None-Match , Last-Modified , If-Modified-Since .

Sources

Spotted a mistake? Email hello@devtoolsmith.com and we’ll fix it.

Frequently asked questions

What does the Cache-Control header do?

The Cache-Control request and response header tells browsers, CDNs and proxies whether a response may be stored, by whom, and for how long. It is the main caching header; when it has max-age, the older Expires header is ignored. The two directives people mix up: no-cache means “store it, but check with the server before every use”, while no-store means “never store it at all”. Requests can send Cache-Control too (a hard reload sends no-cache), but servers and CDNs often ignore it.

How do I see the Cache-Control header?

Run "curl -sI https://example.com" to print a response’s headers, or open the Network tab in your browser’s dev tools and select the request. Paste the headers into the HTTP header checker on this site to get each one explained.