Cache-Control: directives for browsers and shared caches
Cache-Control carries the caching rules for a response: how long it stays fresh, who may store it, and whether a cache must check back with the origin before reusing it.
At a glance
- Direction
- Both. Response directives are the common case; requests can also carry a few.
- Defined in
- RFC 9111
- When absent
- Caches may guess. A response with Last-Modified and a 200 status can be stored and reused heuristically, often for about a tenth of its age.
- Beats
- Expires. Where both are present, max-age wins.
- Shared cache override
- s-maxage applies to shared caches only and overrides max-age for them.
- Private data
- private stops shared caches storing the response; it does not encrypt anything.
- Common pairing
- max-age=31536000, immutable for fingerprinted assets
In detail
The directives split into three jobs. Freshness directives (max-age, s-maxage) say how many seconds a stored copy may be served without asking again. Storage directives (no-store, private, public) say who is allowed to keep a copy at all. Revalidation directives (no-cache, must-revalidate) say what has to happen once the copy is stale. Mixing them up is the source of most caching bugs.
no-cache and no-store are not the same instruction. no-cache permits storage but requires a check with the origin before every reuse, so a 304 still saves the download. no-store forbids keeping the response anywhere, which is what a bank statement or a one time token needs. Sending no-cache when you meant no-store leaves the content on disk.
For versioned assets, the pattern is a filename that changes when the content changes plus max-age=31536000, immutable. The immutable directive tells the browser not to revalidate even when the user presses reload, which removes a burst of conditional requests on every refresh. It is only safe because the URL is guaranteed to change with the bytes.
HTML documents want the opposite: no-cache, or a very short max-age combined with stale-while-revalidate so a shared cache can serve the old copy instantly while it fetches a new one in the background. That keeps a content change visible within seconds while still absorbing traffic spikes at the edge.
Examples
Cache-Control: public, max-age=31536000, immutableSafe only when the filename contains a hash, because nothing will ever ask for that URL again after the content changes.
Cache-Control: no-cacheThe copy is still stored, so a 304 with an ETag makes the check cheap. For something that must never be written to disk at all, the directive is no-store instead.
Cache-Control: max-age=60, s-maxage=600, stale-while-revalidate=86400Things that catch people out
- no-cache does not mean do not cache. The directive that means that is no-store.
- max-age is counted from when the response was generated, not from when the browser received it, so a response arriving with Age: 3500 and max-age=3600 is fresh for one hundred seconds.
- Adding private to an API response does not make it secure. It only stops shared caches storing it, and the browser still keeps a copy.
- must-revalidate only applies once a response is already stale. It does not shorten max-age.
Try it: HTTP Headers Checker
Check a live URL or paste headers you already have. It grades six security headers from A+ to F and lists every other header the response returned.
HTTP Headers Checker
LOCAL · Browser-OnlyView HTTP response headers for any URL
Enter any website URL to fetch and analyze its HTTP response headers. We'll automatically add https:// if needed.
Why Check Security Headers?
Security headers protect your website against common attacks like XSS, clickjacking, and MIME sniffing. They're set by your web server and cost nothing to implement. Missing security headers are one of the most common findings in security audits.
Related HTTP headers
All http headers →ETag: version identifiers for conditional requests
How an ETag is compared, when to use a weak tag, how it drives 304 responses and 412 conflicts, and why compression changes the tag a server sends.
Expires: the older way to set a freshness lifetime
What Expires does, why Cache-Control max-age overrides it, what an invalid date such as 0 means to a cache, and whether it is still worth sending.
Age: how long a cached response has been sitting there
What the Age header counts, how it combines with max-age to give remaining freshness, and how to use it to tell a cache hit from a miss at the edge.
Vary: which request headers change the response
How Vary extends a cache key, why Vary: User-Agent destroys hit rates, what Vary: * means, and the CORS case where forgetting Vary: Origin causes real bugs.
Last-Modified: timestamps as a weak validator
The exact date format Last-Modified requires, why one second resolution makes it a weak validator, and how caches use it to guess freshness.
If-None-Match: asking whether anything changed
How If-None-Match produces a 304, why it uses weak comparison, how it overrides If-Modified-Since, and its second job preventing duplicate creates.
Tools for this
- HTTP Headers Checker Free online HTTP headers checker - view HTTP response headers for any URL
- HTTP Request Builder Free online HTTP request builder - build and test HTTP requests with custom headers and body