no-cache vs no-store: When to Use Each

TL;DRno-cache permits caching but mandates revalidation with the origin before every reuse; no-store forbids storage entirely. Confusing the two either leaks sensitive data through cached copies or floods the origin with redundant full transfers.

Quick-reference header block for the two canonical scenarios:

# Validate-before-serve (freshness replaced by conditional check)
Cache-Control: no-cache, private

# Absolute storage prohibition (regulatory / sensitive data)
Cache-Control: no-store, private

no-cache vs no-store request flow Diagram showing that a no-cache response is stored in cache but every subsequent request triggers a conditional GET to origin; a no-store response is never stored and every request fetches the full payload from origin. Client Shared / Browser Cache Origin no-cache path request If-None-Match / If-Modified-Since 304 (reuse body) or 200 (new body) serve no-store path Client Origin cache bypassed full GET, every time 200 + full payload (always)

Mechanism and RFC 9111 Alignment

no-cache — conditional revalidation required

RFC 9111 Section 5.2.2.4 defines no-cache as follows: a cache must not reuse a stored response to satisfy a new request without first forwarding that request to the origin for validation. This is not a prohibition on storage — it is a prohibition on serving without checking.

The validation exchange uses the stored validators:

  • ETag / If-None-Match — strong or weak entity tag comparison
  • Last-Modified / If-Modified-Since — timestamp comparison

If the origin returns 304 Not Modified, the cache reuses the stored response body without downloading it again. The round-trip costs a few milliseconds but avoids retransmitting the payload. Only when the origin returns 200 OK (with an updated ETag) does the full payload transfer.

This behavior is functionally equivalent to max-age=0, must-revalidate, but no-cache states the intent more clearly and is recommended in RFC 9111 for this purpose.

no-store — storage prohibited at every layer

RFC 9111 Section 5.2.2.5: caches must not store any part of a response — including headers — when no-store is present. This applies to browser memory cache, browser disk cache, CDN edge nodes, reverse proxies, and enterprise gateway caches.

Every request fetches the full payload from origin. No 304 is possible because there is no stored copy to validate against.

Key difference table:

Property no-cache no-store
May be stored in cache? Yes No
Reuse without origin check? No No (nothing stored)
Can save bandwidth via 304? Yes No
Applies to browsers? Yes Yes
Applies to CDNs / proxies? Yes Yes
RFC 9111 section 5.2.2.4 5.2.2.5

Scope and Precedence

no-store holds the highest precedence in the directive resolution order described in Header Stacking and Directive Precedence. It immediately overrides max-age, s-maxage, public, and any freshness extension (stale-while-revalidate, stale-if-error, immutable). Emitting max-age=3600, no-store is valid syntax but the TTL directive is inert — no storage occurs.

no-cache sits in the second precedence tier. It permits storage but forces validation; must-revalidate adds the guarantee that stale entries must not be served even under error conditions (network failure, origin timeout). Without must-revalidate, some caches may serve stale content under RFC 9111’s error-tolerance provisions (Section 4.2.4).

Precedence table (highest to lowest):

Priority Directive Effect
1 no-store Absolute storage prohibition
2 no-cache Storage allowed; revalidation required
3 private Browser-only scope
4 public Shared-cache authorized
5 s-maxage Shared-cache TTL
6 max-age All-cache TTL
7 must-revalidate No stale-on-error after TTL

no-cache applies symmetrically to both private caches (browsers) and shared caches (CDNs). CDNs must not serve a cached no-cache response without forwarding a conditional request to origin. For CDN-specific cache scope interactions, see Public vs Private Cache Scope.

Implementation Patterns

Pattern 1 — Frequently changing API data

An inventory count, exchange rate, or live score is safe to cache but must be fresh on every serve. Bandwidth savings via 304 are significant at scale.

Cache-Control: no-cache, private
ETag: "inv-a3f9"

Every client revalidation sends If-None-Match: "inv-a3f9". If the count has not changed, origin returns 304 — the client reuses the stored body, no payload crosses the wire.

Pattern 2 — Authenticated HTML pages

HTML that varies per user must not be served from a shared cache but may still benefit from browser-level 304 savings:

Cache-Control: no-cache, private
Vary: Cookie

The Vary: Cookie directive prevents CDNs from sharing responses across sessions. no-cache ensures the browser validates on every navigation without transmitting the full HTML body unnecessarily.

Pattern 3 — Session tokens and CSRF state

Tokens must never be retained anywhere. no-store is the correct choice:

Cache-Control: no-store, private
Pragma: no-cache

Including Pragma: no-cache maintains backward compatibility with HTTP/1.0 intermediaries that do not parse Cache-Control.

Pattern 4 — PII, financial records, medical data

Regulatory requirements (HIPAA, PCI DSS) require zero persistence of sensitive payloads:

Cache-Control: no-store, private
X-Content-Type-Options: nosniff

no-store satisfies the “must not retain” requirement. Neither CDN edge nodes nor browser disk cache will hold any part of the response.

Pattern 5 — Authentication pages (/login, /logout)

Form state must not persist between sessions, even in the browser’s back/forward cache:

Cache-Control: no-store, private
Clear-Site-Data: "cache"

Clear-Site-Data: "cache" instructs the browser to evict cached entries for this origin on logout — a belt-and-suspenders approach when no-store alone is not sufficient for post-logout cleanup.

Server and CDN Configuration

Nginx

# Dynamic API — cache but always validate
location /api/ {
    add_header Cache-Control "no-cache, private" always;
}

# Session-bound data — no storage at any layer
location /auth/ {
    add_header Cache-Control "no-store, private" always;
}

# Login / logout pages
location ~ ^/(login|logout) {
    add_header Cache-Control "no-store, private" always;
    add_header Clear-Site-Data '"cache"' always;
}

The always flag ensures the header is emitted on error responses (4xx, 5xx) — without it, Nginx suppresses custom headers on non-2xx responses, which can allow error pages to be cached by intermediaries.

Apache

# Dynamic API — revalidate on every request

    Header always set Cache-Control "no-cache, private"


# Sensitive endpoints — no storage

    Header always set Cache-Control "no-store, private"

Cloudflare (Page Rules / Cache Rules)

Cloudflare maps no-store to a hard BYPASS on its edge. To enforce this via a Cache Rule in the Cloudflare dashboard:

  1. Create a Cache Rule matching the URL pattern (e.g., auth.example.com/session/*).
  2. Set Cache Status to Bypass.
  3. Cloudflare will forward all requests to origin and return CF-Cache-Status: BYPASS in responses.

For no-cache routes, Cloudflare should be configured to respect origin headers rather than override them. Disable any “Edge Cache TTL” override on these paths so Cloudflare honors the origin no-cache instruction and forwards conditional requests.

no-cache + must-revalidate

must-revalidate strengthens no-cache by removing the error-tolerance exception. Without it:

Cache-Control: no-cache

…a cache may serve a stale entry if the origin is unreachable (RFC 9111 Section 4.2.4). Adding must-revalidate:

Cache-Control: no-cache, must-revalidate

…forces the cache to return a 504 Gateway Timeout rather than serve stale content. Use no-cache, must-revalidate for financial data, configuration endpoints, or anything where serving stale is worse than serving an error.

no-store + ETag

Combining these is contradictory. no-store prohibits any storage, which means no ETag can ever be stored or compared. The ETag header is ignored when no-store is present. See freshness vs validation models for guidance on which responses benefit from validators.

no-cache + s-maxage

This combination is valid but niche — it instructs shared caches to store the response but still revalidate on every request, rather than serving from the stored copy during the s-maxage window. The practical result is the same as no-cache alone for CDNs that respect the directive. However, some CDNs use s-maxage to populate their cache index even when no-cache is also present, enabling PURGE API calls to invalidate by URL. Check your CDN’s documentation.

For the full set of directive combinations and how stacking resolves conflicts, see the mastering max-age and s-maxage reference.

Verification Workflow

Step 1 — Inspect response headers

curl -sI https://api.example.com/resource

For no-cache: expect Cache-Control: no-cache and an ETag or Last-Modified header in the response. The absence of validators means revalidation will always result in a 200, defeating the bandwidth-saving purpose of no-cache.

For no-store: expect Cache-Control: no-store. There must be no Age header (an Age header indicates the response came from a cache). At a CDN layer, expect CF-Cache-Status: BYPASS (Cloudflare) or X-Cache: MISS, uncacheable (Fastly).

Step 2 — Verify no-cache revalidation via conditional request

# Extract ETag from first response
ETAG=$(curl -sI https://api.example.com/resource | grep -i etag | awk '{print $2}' | tr -d '\r')

# Send conditional request
curl -sI -H "If-None-Match: ${ETAG}" https://api.example.com/resource

A correctly configured no-cache endpoint returns 304 Not Modified when the resource has not changed. A 200 response is correct when the resource has changed. A 200 response when the resource has not changed indicates the origin is ignoring If-None-Match — fix the origin’s ETag comparison logic.

Step 3 — Confirm no CDN bypass for no-cache

curl -sI https://api.example.com/resource | grep -i "cf-cache-status\|x-cache"

For no-cache, CDN status should be REVALIDATED or MISS (not HIT). A HIT response indicates the CDN is ignoring the no-cache directive and serving a stale copy — check CDN cache rules for TTL overrides.

For no-store, CDN status must be BYPASS or DYNAMIC. Any other status indicates the CDN is storing the response.

Step 4 — Browser DevTools verification

  1. Open DevTools → Network tab.
  2. With caching enabled, load the page once to populate any stores.
  3. Reload without disabling cache.
  4. For no-cache resources: expect 304 Not Modified with a size of (from cache) or similar — the body came from the stored copy, but the request reached the origin.
  5. For no-store resources: expect 200 OK on every load with the full payload size and no from cache annotation.

A 200 from memory cache on a no-cache resource indicates a browser bug or a browser extension intercepting requests.

Failure Modes and Gotchas

  1. WebKit no-cache treated as no-store — Older Safari and WebKit-based browsers have historically treated no-cache as equivalent to no-store for certain cross-origin responses. For sensitive data where any storage risk is unacceptable, use no-store explicitly rather than relying on no-cache semantics.

  2. CDN ignoring no-cache due to positive TTL rule — Many CDN configurations include a blanket “cache everything for X hours” page rule. This overrides origin no-cache instructions. Always verify CDN cache rules do not have positive TTL overrides on paths that emit no-cache.

  3. no-store on responses with Vary — Including Vary on a no-store response is harmless but meaningless. Vary affects how caches partition stored entries; if nothing is stored, the Vary header is ignored. Removing it reduces response header bloat.

  4. Missing validators on no-cache responsesno-cache requires the origin to respond to conditional requests. If the response carries no ETag and no Last-Modified, every revalidation will result in a 200 with the full payload — identical to not caching at all. Emit at least one validator on every no-cache response.

  5. no-cache in request headers vs response headers — RFC 9111 defines no-cache in both request context (the client demands a fresh response, bypassing any cache) and response context (the server requires revalidation). These are distinct semantics. This page covers response-side no-cache. Browser DevTools “Disable cache” checkbox sets request-side no-cache on every request.

  6. Pragma: no-cache inconsistency — HTTP/1.1 deprecated Pragma. It is defined only in request context in RFC 9111 and has no normative response semantics. Some implementations honour it in responses; do not rely on it. Set Cache-Control explicitly.

  7. no-store and service workers — Service workers maintain their own cache (Cache API) that is separate from the HTTP cache. no-store does not prevent a service worker from caching a response. If a service worker intercepts a request and stores the response in its own cache, no-store on the HTTP layer does not prevent that. Sensitive data must be explicitly excluded from service worker caching logic.

FAQ

Q: Does no-cache mean “do not cache”?

No. The name is misleading. no-cache in a response means “you may cache this, but you must validate it with the origin before every use.” The correct directive to prevent caching is no-store.

Q: Should I combine no-cache and no-store in the same header?

Rarely. If you need absolute storage prohibition, use no-store alone — it supersedes no-cache. The only practical reason to combine them is for HTTP/1.0 proxy compatibility: HTTP/1.0 caches do not understand no-store, so adding no-cache alongside covers older intermediaries. In modern infrastructure, no-store alone is sufficient.

Q: Is no-cache equivalent to max-age=0?

Functionally similar but not identical. max-age=0 makes the response immediately stale, which triggers revalidation on the next request — but only if the cache decides to revalidate (which depends on implementation). no-cache makes revalidation mandatory; the cache must not serve without checking even if it considers the entry “fresh.” In practice, treat them as equivalent for browsers, but use no-cache when the intent is “always validate.”

Q: Why does my CDN still cache responses with no-cache?

CDN caching is governed by CDN configuration as well as origin headers. A page rule or cache rule with a positive TTL override will win over origin Cache-Control: no-cache. Review your CDN’s cache rule priority and ensure no rule is applying a TTL to the affected paths.

Q: Can I use no-cache for static assets?

Not typically. Static assets with content-hashed filenames (e.g., app.a3f9d2.js) should use Cache-Control: public, max-age=31536000, immutable — the content hash guarantees freshness; revalidation adds unnecessary latency. Use no-cache only for mutable URLs where the content may change between visits.


Back to Cache-Control Directives & Header Combinations