Cache-Control on pages that set cookies

A response that sets cookies, and is therefore personalised, does not tell caches to keep out. A shared cache could store one user's page or Set-Cookie header and hand it to the next visitor.

Do this: Send Cache-Control: No-store on signed-in pages. A shared cache or proxy can hand one visitor's signed-in page to the next visitor.
PassPersonalised responses are marked no-store or private.
InfoA response that sets cookies lacks no-store or private caching directives.

The fix, in one snippet

Example to adapt Any server, on authenticated responses
Cache-Control: no-store, private

Illustrative values. Change the paths, hostnames and options to match your own site before using it.

The sections below explain what is tested, why it matters and the alternatives.

What we test

This check only applies when the response to / includes at least one Set-Cookie header, which marks it as session-bound or personalised; a response with no cookies passes automatically. Scan.now then reads Cache-Control. The check passes when the header contains no-store, or private (optionally with no-cache). It reports an informational finding when Cache-Control is absent, when it contains public, or when it sets a positive max-age or s-maxage without private, and also when a far-future Expires header stands in for a missing Cache-Control. The combination Set-Cookie plus public is called out specifically because it invites a shared cache to store the cookie itself. Cookie attributes are examined separately in the cookie checks.

Why it matters

HTTP caching (RFC 9111) lets shared caches such as CDNs, corporate proxies and reverse-proxy layers store a response and serve it to anyone who asks for the same URL. A response that varies per user must say so, or the cache has no way to know. The failure modes are concrete: a CDN configured to cache HTML stores the "Welcome back, Alice" page and shows it to Bob, complete with Alice's order history; a proxy caches the Set-Cookie header and hands Alice's session identifier to the next requester, who is now logged in as her. A well-known example was Valve's Steam store in December 2015, when a caching change served other users' account pages for about an hour. Browser caches matter too: after logout, the back button can reveal an account page unless no-store was set. This is informational because most CDNs bypass cache when they see Set-Cookie, but relying on that default is fragile.

How to fix it

State the intent explicitly on every personalised response. no-store is the safe choice for anything authenticated; private permits the user's own browser to cache but keeps shared caches out:

Cache-Control: no-store

Express, applied to authenticated routes:

app.use('/account', (req, res, next) => {
  res.set('Cache-Control', 'no-store');
  next();
});

nginx, for a dynamic location proxied to an application:

location /account/ {
    proxy_pass http://app;
    add_header Cache-Control "no-store" always;
}

Apache: Header always set Cache-Control "no-store" inside a <Location> block. Django sets Cache-Control: private on responses that touch the session and offers the @never_cache decorator; Rails expires_now sends no-store. On Cloudflare, the default caches only static extensions and bypasses responses carrying Set-Cookie; if you enable "Cache Everything", add a Cache Rule that bypasses cache when a session cookie is present. Keep long max-age values for genuinely static assets, which do not set cookies and are where caching belongs.

Where this fits

Cache-Control on pages that set cookies is check 11 of 13 that the website vulnerability scanner runs under http security headers, ordered the way they are worth fixing. That ordering is the point: Fixing this one while the check above it still fails buys less than it looks like.

Fix this one first

Above it in the same category sits X-Powered-By technology disclosure (low), where an X-Powered-By or similar header announces the application platform and often its version (PHP/8.1.2, ASP.NET, Express), narrowing an attacker's search for applicable exploits. An attacker who has that does not need this, so it is the better use of the same hour.

What fixing this still leaves open

Cache-Control on pages that set cookies closes one route in. Immediately below it: Cross-Origin-Resource-Policy, where responses do not carry Cross-Origin-Resource-Policy, so other origins may embed them as scripts, images or fetches in no-cors mode and pull them into their process; X-XSS-Protection (deprecated header), where the response sets X-XSS-Protection to 1 or 1; mode=block.

Found in the same scan

The website vulnerability scanner reports this alongside checks from other categories that are at least as serious, including Certificate chain and hostname validation, where the certificate presented for this hostname did not validate: The chain does not reach a trusted root, an intermediate is missing, the name does not match, or the certificate is self-signed or expired, and Exposed .env configuration file, where a .env configuration file is served from the web root. A single run of website vulnerability scanner answers all of them at once.

Prompt for an AI Hand this check to an assistant Sign in to copy it
The first few lines
You are a senior web engineer. I ran a security and SEO scanner against my site and it reported the finding below. Fix it properly rather than suppressing the symptom.

Finding: Cache-Control on pages that set cookies (info severity)
Scanner check id: cache-control-sensitive
17 more lines, including the evidence and the exact fix

The rest of this prompt names the pages and line numbers we found the problem on, the configuration to change, and the constraints a good answer has to respect. It is free, it just needs an account so the work is not scraped wholesale.

Sign in with Google

Signing in is free and takes one click. We store your email address and nothing else.

References

  1. RFC 9111: HTTP Caching
  2. MDN: Cache-Control
  3. OWASP Session Management Cheat Sheet