Cache-Control

Cache-Control is the HTTP header that governs caching: whether a response may be stored, by whom (browser only, or shared caches like CDNs too), for how long, and what happens when it goes stale. If you configure exactly one header on your site, make it this one.

The directives that matter

DirectiveWhat it does
max-age=3600Fresh for 3600 s — applies to browsers and shared caches.
s-maxage=86400Overrides max-age for shared caches (CDNs) only — see s-maxage.
publicMay be stored by any cache, even when other signals suggest otherwise.
privateBrowser may cache it; CDNs must not. For personalized responses.
no-cacheMay be stored, but must be revalidated (e.g. via ETag) before every use. Not "don't cache".
no-storeThe actual "don't cache": nothing may be written to any cache.
immutableWon't change during its lifetime — browsers skip revalidation on reload. For fingerprinted assets.
stale-while-revalidate=60Serve stale for up to 60 s while refreshing in the background — see stale-while-revalidate.

A pattern that covers most sites

# HTML — short in the browser, long at the edge, purge + warm on deploy
Cache-Control: public, max-age=60, s-maxage=86400

# Fingerprinted assets (app.3f9a2c.js) — cache forever, never revalidate
Cache-Control: public, max-age=31536000, immutable

The split matters: browsers you can't purge, CDNs you can. A short max-age keeps browsers honest; a long s-maxage gives the CDN a high hit ratio; a purge plus re-warm on deploy keeps content correct.

What Cache-Control can't do

It sets the rules, not the reality. A CDN may cap or override your TTLs, ignore directives it doesn't support, or evict your object early under cache pressure. And some CDNs need explicit opt-in before they cache HTML at all — Cloudflare famously marks HTML DYNAMIC by default no matter what Cache-Control says. Verify what actually happens at the edge with our cache checker.