All posts

no-cache vs no-store, which Cache-Control directive keeps data private

CentralCSP Team ·

Last update:

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 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

Both directives come from RFC 9111, 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 (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."

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 (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.

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 (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.

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

DirectiveMay be stored?Reused without asking the server?Which caches
no-storeNoNever storedPrivate and shared
no-cacheAllowed (disk or memory)No, revalidates firstPrivate and shared
privateBrowser onlyYes, while freshPrivate only, shared must not store

The back/forward 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 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.

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

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.

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 flags them so you can add no-store before it becomes a leak.

FAQ

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?

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?

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?

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?

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.

Sources