Cache-Control Cheatsheet 2026
Updated April 2026
max-age=31536000, immutable for static assets; no-cache for HTML pages that should always be revalidated; no-store for sensitive data that must never be cached; stale-while-revalidate for background refresh without latency.
Every Cache-Control directive in one place — what it does, when to use it, and the exact config for Nginx, Cloudflare, and Vercel.
Quick reference
| Directive | Who respects it | What it does | When to use |
|---|---|---|---|
max-age=N | Browser + CDN | Fresh for N seconds | All cacheable resources |
s-maxage=N | CDN only | CDN-specific TTL, overrides max-age | Different CDN vs browser TTL |
no-cache | Browser + CDN | Store but revalidate before every serve | HTML pages, frequently updated resources |
no-store | Browser + CDN | Do not store at all | Authenticated responses, sensitive data |
private | CDN only | Browser can cache, CDN cannot | User-specific responses |
public | CDN | CDN allowed to cache | Static assets, public API responses |
immutable | Browser | Never revalidate during max-age | Fingerprinted/hashed static assets |
stale-while-revalidate=N | Browser + CDN | Serve stale for N seconds while fetching fresh in background | APIs, pages where some staleness is OK |
stale-if-error=N | Browser + CDN | Serve stale if origin returns error for N seconds | High-availability sites |
must-revalidate | Browser + CDN | Must check origin once stale — no serving stale on error | Pricing, inventory, time-sensitive data |
Common patterns
Fingerprinted static assets (JS, CSS, images)
Cache-Control: public, max-age=31536000, immutable
1 year TTL. immutable skips revalidation on refresh. Only works when the filename changes on update (e.g. app.a3f9d.js).
HTML pages
Cache-Control: no-cache
Stored in cache but revalidated every request using ETag or Last-Modified. Fast for unchanged pages, always fresh when changed.
Authenticated API responses
Cache-Control: no-store
Nothing stored anywhere. Every request goes to origin. Use for anything containing user-specific data.
Public API with background refresh
Cache-Control: public, max-age=60, stale-while-revalidate=300
Fresh for 60s. For the next 300s after expiry, serve stale immediately while fetching fresh in background. Zero user-visible latency on revalidation.
High-availability with error fallback
Cache-Control: public, max-age=300, stale-if-error=86400
If origin is down, serve cached response for up to 24 hours instead of showing an error page.
CDN-specific TTL (different from browser)
Cache-Control: public, max-age=60, s-maxage=3600
Browser caches for 60s. CDN caches for 1 hour. Useful for content that changes infrequently but you want browsers to check regularly.
Nginx config examples
# Static assets — 1 year immutable
location ~* \.(js|css|woff2|png|jpg|svg|ico)$ { add_header Cache-Control "public, max-age=31536000, immutable" always;
}
# HTML — revalidate every request
location ~* \.html$ { add_header Cache-Control "no-cache" always;
}
# API — never cache
location /api/ { add_header Cache-Control "no-store" always;
}
Cloudflare examples
# Cache Rules → Create rule # Match: File extension is js, css, woff2, png, jpg # Action: Edge Cache TTL → 1 year # (immutable is set by the origin header — Cloudflare respects it)
Vercel (vercel.json)
{ "headers": [ { "source": "/static/(.*)", "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] }, { "source": "/api/(.*)", "headers": [{ "key": "Cache-Control", "value": "no-store" }] } ]
}
Common mistakes
- no-cache is not "don't cache" — it means "cache but always revalidate". Use no-store if you really mean don't store it.
- immutable without fingerprinting — if the filename doesn't change on update, browsers will never fetch the new version during the max-age window.
- public on authenticated responses — CDNs will cache and serve one user's response to another. Always use private or no-store for user-specific data.
- Missing Vary: Accept-Encoding — if you serve compressed content, add Vary: Accept-Encoding so CDNs cache separate copies for compressed vs uncompressed clients.