A header set to "no-cache" doesn't mean "don't cache this" — it means "cache this, but check with the server before using the cached copy every time" — and this single, widely-misunderstood directive is responsible for a large fraction of "why is this still cached" confusion
The previous articles on this site covered HTTP caching (Cache-Control, ETags), security headers, and CORS. This article addresses specific Cache-Control directive combinations and misconceptions — particularly the no-cache vs no-store distinction, which is frequently confused, despite being one of the most consequential differences in how browsers/caches behave.
no-cache: caches are allowed to store the response — they just must revalidate first
Despite its name, Cache-Control: no-cache permits storing the response in a cache — what it prohibits is using the cached copy without first checking with the origin server (a process called revalidation, typically using ETags or Last-Modified headers, covered in the previous caching article).
The practical effect: a resource with no-cache is stored, but *every subsequent request must include a conditional request (using the cached ETag/Last-Modified value) to the origin server — the server responds with either 304 Not Modified (if the resource hasn't changed — in which case the cached body is used, avoiding re-downloading the full content) or a fresh 200 OK with new content (if it has changed).
no-cache is, essentially, "always revalidate" — not "never cache" — it still provides bandwidth savings (the body isn't re-downloaded if unchanged, thanks to 304 responses) — but requires a round-trip to the server for every request, unlike max-age-based caching (where, within the max-age window, the cached copy is used directly, with no server round-trip at all).
no-store: the actual "don't cache this" directive
Cache-Control: no-store means: the response must NOT be stored in any cache, at all — not in browser caches, not in intermediate/proxy caches — **every request must go fully to the origin server, and the response is used, displayed, and then discarded — not retained for any future use.
no-store is the directive for genuinely sensitive/dynamic content — banking information, personalized data that shouldn't persist in any cache (even temporarily, even for revalidation purposes) — responses that must never be reused, under any circumstances, even if "unchanged."
The frequent confusion: developers intending "don't cache this (sensitive) content" sometimes use no-cache — which, as described, still stores the response — while it does require revalidation (providing some protection — the server is consulted on every request), it's not the "absolutely never stored anywhere" guarantee that no-store provides. For genuinely sensitive content, no-store (often combined with no-cache and private, for maximum clarity/compatibility — Cache-Control: no-store, no-cache, must-revalidate, private is a commonly-seen "maximum strictness" combination, though no-store alone is, technically, the strongest individual directive) is the more appropriate choice.
private vs public: who's allowed to cache?
Cache-Control: private indicates the response is specific to one user (e.g., a personalized page, reflecting the requesting user's account/session) — private responses may be cached by the user's own browser, but must NOT be cached by shared/intermediate caches (CDNs, corporate proxy caches, ISP-level caches) — since a shared cache serving this response to a different user would be serving that different user content intended for the original user — a significant privacy/correctness issue.
Cache-Control: public indicates the response is the same for everyone — suitable for caching by shared/intermediate caches as well as the requesting browser.
The consequence of omitting private for personalized content cached by a shared CDN/proxy: user A's personalized page might be cached by a shared cache (which doesn't know "this is personalized, don't share it" without the private directive) — and subsequently served to user B, when user B requests what appears, to the cache, to be "the same" URL — resulting in user B seeing user A's personalized content — a genuine, documented class of privacy/security incident, underscoring why "private" vs "public" isn't a minor technicality.
must-revalidate: what happens when a stale cached response can't reach the server
max-age=N specifies how long a cached response is considered "fresh" — after N seconds, the response is "stale."
Without must-revalidate: some caches, upon encountering a stale response, and being unable to reach the origin server (network issues, server downtime) — may serve the stale response anyway ("better than nothing" — some HTTP caching specifications permit, under certain conditions, serving "stale" content when revalidation isn't possible, as a resilience measure).
must-revalidate instructs caches: do NOT serve a stale response under any circumstances — if the cached copy is stale, and the origin server can't be reached to revalidate — the cache must return an error (rather than the stale, potentially-outdated content) — appropriate for content where serving outdated information is worse than serving an error (e.g., real-time pricing/availability information, where "stale" data could lead to incorrect decisions based on it).
Vary: caching the "same" URL differently based on request headers
The Vary response header indicates that the cached response depends on one or more request headers — e.g., Vary: Accept-Language indicates the response content differs based on the requesting client's Accept-Language header (the server returns different-language content for the same URL, depending on what language the client requested).
Why this matters for caching: without Vary, a cache might store one version of the response (e.g., the English version, served to the first requester) and serve that same cached version to subsequent requesters, regardless of their own Accept-Language header — a French-speaking user, requesting the same URL after an English-speaking user's request was cached, would receive the cached English version — incorrect, from the French-speaking user's perspective.
Vary: Accept-Language tells caches: "store separate cached copies, keyed by Accept-Language value, for this URL" — ensuring each language-variant is cached and served correctly, independently.
A commonly-overlooked Vary use case: Vary: Accept-Encoding — responses might be served compressed (gzip, brotli) or uncompressed, depending on the client's Accept-Encoding header — without Vary: Accept-Encoding, a cache could serve a compressed response to a client that doesn't support (or didn't request) that compression — resulting in the client receiving garbled, undecoded compressed data (since the client, not expecting compressed content, wouldn't decompress it).
How to use the HTTP Headers tool on sadiqbd.com
- Check
Cache-Controldirective combinations on responses — particularly distinguishingno-cache(stored, but revalidated every time) fromno-store(never stored at all) — for sensitive content, verifyno-storeis present, not justno-cache - For personalized content: verify
privateis present (notpublic, and not absent — absence of either may, depending on other directives/defaults, be ambiguous) — particularly important for content served through CDNs/shared caches - Check for
Varyheaders on content that varies by language/encoding/other request headers — absence of an appropriateVaryheader, for content that genuinely varies, can indicate a caching-correctness issue (even if it hasn't yet manifested as a visible bug — it may simply not have been triggered yet, depending on request patterns)
Frequently Asked Questions
If no-cache still allows storage, what's the practical difference from max-age=0?
max-age=0 also means the response is immediately "stale" (0 seconds of freshness) — requiring revalidation before reuse — in practice, Cache-Control: no-cache and Cache-Control: max-age=0 often produce similar behavior (both require revalidation on every request) — though historically, some differences in how various caches/browsers interpret these directives (particularly combined with other directives, or in edge cases involving older HTTP/1.0-era caches, which no-cache was, in part, designed with in mind) have existed — for modern purposes, the two are generally treated similarly by current browsers, but no-cache remains the more commonly-used, more widely-recognized directive for expressing "always revalidate" intent.
Is the HTTP Headers tool free? Yes — completely free, no sign-up required.
Try the HTTP Headers tool free at sadiqbd.com — inspect Cache-Control, Vary, and all response headers for any URL instantly.