Despite the name, no-cache allows a cache to store a response, but not to reuse it without checking with the origin first. no-store is the directive that tells caches not to store it at all. Mixing these up is a common way to leak private pages through shared caches.
The directives that matter
RFC 9111 defines these response directives: max-age, must-revalidate, must-understand, no-cache, no-store, no-transform, private, proxy-revalidate, public and s-maxage. In short:
- no-store. A cache must not store any part of the request or the response. The RFC notes it is not a reliable privacy mechanism on its own.
- no-cache. The stored response must not be used to satisfy a later request without successful validation with the origin.
- max-age. Freshness lifetime in seconds.
- s-maxage. Applies to shared caches only and overrides max-age and Expires there.
- must-revalidate. Once stale, the response needs origin validation before reuse. A cache that cannot reach the origin should return an error, ideally 504, instead of serving stale data.
- public. Allows storage even where it would otherwise be prohibited, such as for responses to authenticated requests.
- private. Shared caches must not store it. A browser cache may.
Note that immutable, a directive you may have seen, is not part of RFC 9111.
How freshness is computed
A response is fresh while its freshness lifetime exceeds its current age. Lifetime comes from the first applicable source: s-maxage for shared caches, then max-age, then Expires minus Date, then heuristics. If a directive appears more than once, a cache either uses the first or treats the response as stale.
Heuristics apply only when no explicit expiration exists. The RFC suggests basing it on Last-Modified, for example about ten percent of the time since the last change, but mandates no algorithm. Set explicit values so you never depend on a guess.
The Age header is the cache's estimate of seconds since the origin generated or validated the response.
Validation and Vary
A stale response can be revalidated with a conditional request using ETag and If-None-Match, or Last-Modified and If-Modified-Since. A 304 refreshes the stored copy. Details are in ETags and conditional requests.
Vary names request headers that must match for a stored response to be reused. A Vary of * never matches. Forgetting Vary on a response that depends on, say, a language or encoding header is a classic way to serve the wrong variant. If a response depends on a cookie or authorization, keep it out of shared caches.
What to set for common cases
Use these as starting points and test them:
# Static fingerprinted assets
Cache-Control: public, max-age=31536000
# Personalized pages, account data, API responses with user data
Cache-Control: private, no-store
# Public content that must be checked every time
Cache-Control: no-cache
Invalidation
Unsafe methods such as PUT, POST and DELETE cause stored responses for the target URI to be invalidated after a non-error response. This does not reach other URIs that show the same data, so design your URLs with care.
For the attack side, see CDN cache poisoning prevention.
Where Safeguard fits
This topic sits below where Safeguard works. Safeguard helps with the application side: scanning dependencies for known vulnerabilities, including in the networking, TLS and HTTP libraries you pull in, and gating pull requests with policies. Start with the free scan at gold.safeguard.sh/scan.
Quick check
Which directive stops a response being stored?
no-store. The no-cache directive allows storage but requires validation before reuse.
What does private do?
It forbids shared caches from storing the response, while a private cache such as a browser may keep it.
Sources
Last reviewed: 19 October 2026.