# no-cache vs no-store, which Cache-Control directive keeps data private (/en/blog/no-cache-vs-no-store)



The names sound like synonyms, and that is the trap. `no-cache` does not mean "do
not cache." It means "store it, but check with the server before reusing it." The
directive that actually keeps a response out of caches is `no-store`, and for a
page with personal data that difference is the whole ballgame.

If you have a response carrying a session ID or personal data, the
[`Cache-Control`](/en/docs/web-security/security-headers/cache-control) directive
you want is `no-store`. `no-cache` still lets the browser and shared caches keep
the bytes, it only forces a revalidation before reuse. For confidentiality,
stored-but-revalidated is not good enough; you want never-stored.

## no-cache vs no-store, what each one tells a cache [#no-cache-vs-no-store-what-each-one-tells-a-cache]

Both directives come from [RFC 9111](https://httpwg.org/specs/rfc9111.html), the
HTTP caching specification, and it defines them precisely. The confusion is entirely
in the names, so start with what each one actually instructs a cache to do.

There are two tiers of cache to keep in mind. A private cache is the user's browser.
A shared cache is a corporate proxy, a CDN, or a reverse proxy that stores one
response and reuses it for many users. HTTP is designed to cache aggressively, so a
response with no `Cache-Control` header at all can still be stored and reused
through heuristic caching (RFC 9111 section 4.2.2, with a heuristic lifetime
typically around 10% of the resource's `Last-Modified` age). And the spec is
explicit that `Set-Cookie` does not inhibit caching: a cacheable response with a
`Set-Cookie` header can be, and often is, used to satisfy other requests. If you
want a response kept out of caches, you have to say so.

## no-cache stores the response, then revalidates [#no-cache-stores-the-response-then-revalidates]

`no-cache` (RFC 9111 section 5.2.2.4) means a cache **may** store the response but
**must not** reuse it without a successful revalidation with the origin server.
Storage is permitted, not mandated, so the copy can sit on disk or in memory. On
the next request the cache asks the server "is my copy still good?" and reuses it
only on a 304. MDN states it verbatim: "no-cache does not mean don't cache."

```http
Cache-Control: no-cache
```

For confidentiality this does nothing. The response can be sitting in the cache,
and anyone with access to it, the shared proxy or the user's disk on a shared
machine, can read the stored copy. Revalidation controls freshness, not who can see
the bytes. It also does not bind your edge. Cloudflare respects origin
`Cache-Control` by default, but cache rules and edge TTL settings can augment or
override it, so `no-cache` at the origin does not constrain a misconfigured CDN
layer.

## no-store keeps the response out of every cache [#no-store-keeps-the-response-out-of-every-cache]

`no-store` (RFC 9111 section 5.2.2.5) is the strict one. A cache "MUST NOT store
any part of either the immediate request or the response and MUST NOT use the
response to satisfy any other request." That applies to private and shared caches
alike.

```http
Cache-Control: no-store
```

This is the directive for any response containing a session identifier or personal
data, and it is what OWASP's Session Management cheat sheet prescribes for
responses carrying session IDs. With `no-store`, the extra directives and the
`Expires` header people pile on are generally unnecessary for modern browsers. The
legacy incantation `private, no-cache, no-store, max-age=0, must-revalidate` is,
per MDN's own example, equivalent to plain `no-store`, because the most restrictive
directive wins.

One honest caveat from the spec itself: RFC 9111 warns that `no-store` "is not a
reliable or sufficient mechanism for ensuring privacy," because malicious or
non-compliant caches might not obey it. It is an instruction to well-behaved
caches, not encryption. Use it, and do not treat it as the only control.

## private only stops shared caches [#private-only-stops-shared-caches]

`private` (RFC 9111 section 5.2.2.7) says shared caches must not store the response,
but the browser's own cache still may. It scopes a personalized response to one
user by keeping it out of proxies and CDNs, which is useful, but it is not a
confidentiality control against local storage of the bytes. `private` on its own
still leaves the response on the user's disk.

```http
Cache-Control: private, no-cache
```

That combination, `private, no-cache`, is a reasonable middle ground for a
personalized page that is fine to cache in the browser but must revalidate and must
never touch a shared cache. It is not the setting for a session token; that is
still `no-store`.

Getting this tier wrong has a track record. Web cache deception attacks work
precisely when dynamic per-user content is storable by a shared cache, and the
defense is `no-store` or `private` on dynamic responses. In the Steam incident of
Christmas 2015, a caching layer served authenticated store pages to the wrong
users, and pages belonging to about 34,000 users, some including billing
addresses and partial card digits, were shown to other customers.

## no-cache vs no-store vs private, side by side [#no-cache-vs-no-store-vs-private-side-by-side]

| Directive  | May be stored?           | Reused without asking the server? | Which caches                        |
| ---------- | ------------------------ | --------------------------------- | ----------------------------------- |
| `no-store` | No                       | Never stored                      | Private and shared                  |
| `no-cache` | Allowed (disk or memory) | No, revalidates first             | Private and shared                  |
| `private`  | Browser only             | Yes, while fresh                  | Private only, shared must not store |

## The back/forward cache plays by different rules [#the-backforward-cache-plays-by-different-rules]

The back/forward cache (bfcache) is not an HTTP cache. It keeps a live snapshot of
a page so navigating back is instant, and because it restores the snapshot without
revalidating, `Cache-Control` only partially governs it. MDN notes that even
`no-cache` does not guarantee revalidation for history navigations.

Chrome now restores even `no-store` pages from bfcache (the rollout to all users
completed in 2025), with mitigations: the page is not restored if it used
WebSocket, WebTransport, or WebRTC, the entry is evicted when cookies or other
authorization methods change or when a subresource fetch comes back with
`no-store`, and the entry is capped at 3 minutes instead of the usual 10. Chrome's documentation
notes that other browsers may still block bfcache when `Cache-Control: no-store`
is present, so do not assume uniform behavior across engines.

The security consequence is concrete: a user who logs out and hits the back button
can be shown the authenticated page again. So logout has to actually invalidate the
session, changing or clearing the [session cookie](/en/docs/web-security/security-headers/cookie-security)
server-side, not just navigate away. Because bfcache eviction is tied to a cookie
change, clearing the session cookie is what makes the restored page safe, not the
cache directive. OWASP additionally recommends clearing client state on logout with
a `Clear-Site-Data` header.

```http
Clear-Site-Data: "cache", "cookies", "storage"
```

The directives are quoted strings and the header only works over HTTPS. Support for
`"cookies"` and `"storage"` is broad; support for `"cache"` is uneven across
engines, so treat it as an extra layer rather than the mechanism. For browsers
where a bfcache restore is still possible, web.dev suggests a belt-and-suspenders
check: listen for the `pageshow` event, verify a site-specific cookie is still
present, and force a reload if it is gone.

The mistake also runs in reverse. Do not use `no-store` as a bfcache blocker on
ordinary pages. web.dev's guidance is to reserve `no-store` for pages where caching
of any sort is never appropriate, and to use `no-cache` or `max-age=0` elsewhere,
since those do not affect a page's bfcache eligibility. Blanket `no-store` kills
instant back/forward navigation for no security gain.

## Recommendation [#recommendation]

Set caching per response, not site-wide. Send `Cache-Control: no-store` on any
response that carries a session ID or sensitive data, login, account, checkout,
anything personalized. Keep long-lived caching where it belongs, on static
fingerprinted assets, with `max-age=31536000, immutable`.

```http
Cache-Control: max-age=31536000, immutable
```

One note on `immutable`: Firefox and Safari honor it, Chrome and Edge ignore it.
Ignoring it is harmless, the `max-age` still applies, so the pattern stays correct
everywhere.

And pair `no-store` with a real logout: invalidate the session server-side, clear
the session cookie, and send `Clear-Site-Data` so a restored bfcache page cannot
show authenticated content. To find responses that set or require a session cookie
but carry no directive keeping them out of shared caches, CentralCSP's free
[security headers scanner](/tools/security-headers) flags them so you can add
`no-store` before it becomes a leak.

## FAQ [#faq]

### Why is Cache-Control no-cache not working? [#why-is-cache-control-no-cache-not-working]

It usually is working, exactly as specified. `no-cache` permits storage and permits
reuse after a 304 revalidation, so cached copies on disk and fast repeat loads are
expected. Back-button restores can bypass it entirely, because the bfcache restores
a snapshot without revalidating. And a CDN layer with its own cache rules may
override origin headers. If the behavior you wanted was "never stored," the
directive you wanted was `no-store`.

### Does no-cache prevent caching? [#does-no-cache-prevent-caching]

No. It prevents reuse without revalidation, not storage. MDN says it directly: if
the sense of "don't cache" you want is actually "don't store," then `no-store` is
the directive to use.

### Which Cache-Control directive should I use for sensitive data? [#which-cache-control-directive-should-i-use-for-sensitive-data]

`Cache-Control: no-store` on every response carrying a session ID or personal data.
That is OWASP's recommendation, and the extra directives and `Expires` header are
generally unnecessary for modern browsers.

### Can the back button show a page that was sent with no-store? [#can-the-back-button-show-a-page-that-was-sent-with-no-store]

Yes, in Chrome. `no-store` keeps the response out of HTTP caches, but Chrome
restores even `no-store` pages from the bfcache, with eviction on cookie changes
and a 3 minute cap. The back button can still show the page unless logout changed
or cleared the session cookie. Pair server-side session invalidation with
`Clear-Site-Data`, and optionally a `pageshow` cookie check that reloads the page.

### Should I use no-store on every page? [#should-i-use-no-store-on-every-page]

No. Reserve `no-store` for responses where caching of any sort is never
appropriate, anything carrying a session ID or personal data. On ordinary pages it
forces a full re-download on every request and can block back/forward cache
restores in some engines, so navigation gets slower with no security gain. Use
`no-cache` or `max-age=0` instead.

## Related reading [#related-reading]

* [How to improve your security headers grade](/en/blog/improve-security-headers-grade)
* [SameSite vs CSRF tokens, which protects your sessions](/en/blog/samesite-vs-csrf-tokens)
* [Cache-Control reference](/en/docs/web-security/security-headers/cache-control)
* [Cookie security reference](/en/docs/web-security/security-headers/cookie-security)

## Sources [#sources]

* [RFC 9111, HTTP Caching](https://httpwg.org/specs/rfc9111.html)
* [MDN, Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cache-Control)
* [MDN, HTTP caching guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Caching)
* [Chrome, bfcache and Cache-Control no-store](https://developer.chrome.com/docs/web-platform/bfcache-ccns)
* [web.dev, Back/forward cache](https://web.dev/articles/bfcache)
* [OWASP, Session Management cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
* [OWASP WSTG, Testing for Browser Cache Weaknesses](https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/04-Authentication_Testing/06-Testing_for_Browser_Cache_Weaknesses)
* [MDN, Clear-Site-Data](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Clear-Site-Data)
* [PortSwigger, Web cache deception](https://portswigger.net/web-security/web-cache-deception)
* [Cloudflare, Cache-Control at the edge](https://developers.cloudflare.com/cache/concepts/cache-control/)
