The fix, in one snippet
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.
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
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 GoogleSigning in is free and takes one click. We store your email address and nothing else.