Cache-Control and Clear-Site-Data for Sensitive Pages

This guide is part of the Web Security Headers Fundamentals reference and treats Cache-Control and Clear-Site-Data as the two controls that govern where authenticated data is allowed to persist — in intermediary caches, on disk, in the back/forward cache, and in client-side storage after a session ends. The two operational decisions that carry the most blast radius are which routes get no-store and what to purge on logout. The first is dissected in Cache-Control: no-store for sensitive pages; the second in using Clear-Site-Data on logout.

Threat Model & Protocol Mechanics

The threat these headers neutralize is not interception in transit — that is the job of HSTS — but persistence at rest on the client and in shared infrastructure. An account summary, a bank statement, a medical record, or a page echoing a session token is a sensitive object the moment it reaches the browser. If that object is allowed to be stored, it outlives the request that produced it, and the attacker no longer needs the network.

Four concrete leak paths matter:

Cache-Control is defined by RFC 9111 (HTTP Caching, which obsoletes RFC 7234). Its security-relevant response directives are:

Clear-Site-Data is defined by the W3C Clear-Site-Data specification. It is a response header whose value is a comma-separated list of quoted data types the browser must purge for the response’s origin:

The browser only honors Clear-Site-Data over a secure (HTTPS) transport; it is ignored on plaintext. Support is good in Chromium and Firefox; Safari’s support is partial and lagging — never rely on it as your only logout cleanup mechanism. The decision logic for selecting directives per page sensitivity is below.

Choosing Cache-Control by page sensitivity and Clear-Site-Data on logout A decision tree mapping page sensitivity to no-store, private, or public caching, plus a logout branch that emits Clear-Site-Data to purge cookies and storage. Response to classify Contains per-user or secret data? no public, max-age static asset yes Must not survive the request or back button? yes no-store no private, no-cache, must-revalidate On logout response: Clear-Site-Data "cookies", "storage"
Sensitivity drives the Cache-Control directive; the logout response is where Clear-Site-Data purges residual cookies and storage.

The two headers are complementary, not interchangeable. Cache-Control governs storage during the authenticated session; Clear-Site-Data purges what already accumulated at the end of it. A complete posture sets no-store on sensitive responses and emits Clear-Site-Data on logout. Neither replaces transport encryption (HSTS) or content restriction (Content Security Policy).

Directive Syntax & Spec

Cache-Control: no-store
Cache-Control: private, no-cache, must-revalidate
Clear-Site-Data: "cache", "cookies", "storage"

Cache-Control directives are comma-separated, case-insensitive, and order-independent. Clear-Site-Data types are comma-separated quoted strings — the double quotes are part of the wire syntax and the header is ignored if they are omitted.

Directive Type Default Security Impact When to Deviate
Cache-Control: no-store Response flag off No cache stores the response; also keeps the page out of BFCache. The only directive that prevents on-disk persistence. Apply only to sensitive routes; site-wide it destroys cacheability and CDN offload
Cache-Control: no-cache Response flag off Response may be stored but must be revalidated before reuse. Does not prevent storage or BFCache. Use for resources that change often but are safe to cache when fresh
Cache-Control: private Response flag off Browser may cache; shared/intermediary caches may not. Stops cross-user leakage in proxies. Use for per-user pages that are safe on the user’s own disk
Cache-Control: must-revalidate Response flag off Forbids serving a stale cached copy after expiry without revalidation. Pair with private/max-age=0 to prevent stale authenticated views
Clear-Site-Data: "cache" Quoted token n/a Purges the origin’s HTTP cache. Logout, or after a security event
Clear-Site-Data: "cookies" Quoted token n/a Purges cookies and HTTP auth credentials for the origin/registrable domain. Logout; primary token for session teardown
Clear-Site-Data: "storage" Quoted token n/a Purges localStorage, sessionStorage, IndexedDB, Cache Storage, Service Workers. Logout when client state holds user data
Clear-Site-Data: "*" Quoted token n/a Purges all defined types including executionContexts — can force-reload open tabs. Full account wipe; avoid when other tabs must stay live

Common malformed-syntax gotchas:

Platform-Specific Implementation

Set these headers per route. A site-wide no-store is a self-inflicted performance outage; route the directives so static assets stay cacheable and only authenticated responses are protected.

Nginx

# Sensitive authenticated routes only
location ~ ^/(account|banking|settings) {
    add_header Cache-Control "no-store" always;
}

# Logout endpoint purges client state
location = /logout {
    add_header Clear-Site-Data "\"cache\", \"cookies\", \"storage\"" always;
    add_header Cache-Control "no-store" always;
}

The always flag emits the header on 4xx/5xx responses too, so an error page rendered on a sensitive route is not accidentally cacheable. Note that an add_header in a location replaces all inherited headers — re-declare any other security headers you need in these blocks. Verify: curl -sI https://example.com/account | grep -i cache-control

Apache

<LocationMatch "^/(account|banking|settings)">
    Header always set Cache-Control "no-store"
</LocationMatch>

<Location "/logout">
    Header always set Clear-Site-Data "\"cache\", \"cookies\", \"storage\""
    Header always set Cache-Control "no-store"
</Location>

always ensures the directive is appended even on internally generated error documents, which the default onsuccess table skips. Requires mod_headers. Verify: apachectl -M | grep headers then curl -sI https://example.com/logout | grep -i clear-site-data

IIS

<location path="account">
  <system.webServer>
    <httpProtocol>
      <customHeaders>
        <add name="Cache-Control" value="no-store" />
      </customHeaders>
    </httpProtocol>
  </system.webServer>
</location>

IIS scopes customHeaders with the <location path> element so only the matched route receives no-store; a header placed at the site root would apply everywhere. Remove any conflicting Cache-Control set by static-content modules on the same path. Verify in IIS Manager → HTTP Response Headers, then confirm with curl -sI.

Node/Express (Helmet)

// Helmet does not own Cache-Control; set it per route
app.get('/account', (req, res) => {
  res.set('Cache-Control', 'no-store');
  res.render('account');
});

app.post('/logout', (req, res) => {
  req.session.destroy();
  res.set('Clear-Site-Data', '"cache", "cookies", "storage"');
  res.set('Cache-Control', 'no-store');
  res.redirect('/');
});

Set the headers inside the route handler (not a blanket app.use) so only sensitive responses carry no-store; a global middleware would strip caching from your entire asset pipeline. Behind a TLS-terminating proxy, Clear-Site-Data is only honored if the response reaches the browser over HTTPS. Verify: curl -sI https://localhost:3000/account | grep -i cache-control

Cloudflare

Use a Response Header Transform Rule scoped by URI path. In Rules → Transform Rules → Modify Response Header, match URI Path starts with /account and set Cache-Control to no-store; add a second rule matching /logout that sets Clear-Site-Data. Critically, set Cache Rules → Bypass cache on the same path expression — otherwise Cloudflare’s edge may cache the response before your origin’s no-store is honored, since the edge applies its own cache logic. Verify: curl -sI https://example.com/account | grep -i cache-control

Verification & Diagnostic Workflows

A no-store that your origin emits but the CDN strips is worse than none — it gives false assurance. Verify at the edge the client actually talks to.

Header presence and exact value:

curl -sI https://example.com/account | grep -iE 'cache-control|clear-site-data'
# cache-control: no-store

Logout teardown:

curl -sI https://example.com/logout | grep -i clear-site-data
# clear-site-data: "cache", "cookies", "storage"

Confirm a shared cache is not retaining the page — request through the CDN edge and inspect the cache-status header:

curl -sI https://example.com/account | grep -iE 'cf-cache-status|age|x-cache'
# cf-cache-status: BYPASS
# (no Age header on a never-cached response)

A non-zero Age or a HIT/cf-cache-status: HIT on an authenticated route means a shared cache stored it — a leak.

Browser DevTools: open DevTools → Application → Storage. After hitting a no-store route, the Network panel’s request row should show (memory cache)/(disk cache) only for assets, never for the document. After logout, Application → Cookies and Application → Local Storage / Session Storage / IndexedDB for the origin should be empty. The Application → Back/forward cache panel (Chrome) reports whether the page is BFCache-eligible; a no-store document is reported as not eligible, which is the goal for authenticated pages.

BFCache check from the keyboard: load the authenticated page, log out, press the browser Back button. The page must not restore an authenticated view from memory. If it does, the document response lacked no-store.

CI/CD regression gate:

hdr=$(curl -sI https://example.com/account | grep -i '^cache-control:')
echo "$hdr" | grep -qi 'no-store' \
  || { echo "Sensitive route lost no-store"; exit 1; }

Troubleshooting, Misconfigurations & Safe Rollback

Frequently Asked Questions

What is the difference between no-store and no-cache? no-store forbids any cache from storing the response and keeps it out of the back/forward cache — nothing persists. no-cache permits storage but requires revalidation with the origin before reuse; it does not stop on-disk persistence or BFCache. For sensitive pages you almost always want no-store.

Does Cache-Control: no-cache keep a page out of the back/forward cache? No. Only no-store reliably makes a document BFCache-ineligible. If pressing Back after logout restores an authenticated view, the page was served with something other than no-store.

Does Clear-Site-Data work over plain HTTP? No. The browser only honors it on a secure transport. Serve it over HTTPS, which is why pairing it with HSTS matters. It is also not universally supported, so it supplements — never replaces — server-side session invalidation.

Is Pragma: no-cache still needed? No. Pragma is an HTTP/1.0 request-header mechanism; modern browsers do not treat a Pragma response header as authoritative for caching. Send proper Cache-Control directives. Keep Pragma only as a defensive note for ancient intermediaries; it is not a substitute for no-store.

Should I set no-store for the whole site? No. A site-wide no-store eliminates browser caching, CDN offload, and BFCache for every page, degrading performance for no security gain on public content. Scope it to routes that render per-user or secret data.