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
Cache-Control: public, max-age=31536000, immutable
Cache-Control: no-cache
Cache-Control: public, max-age=0, s-maxage=60, stale-while-revalidate=30
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.