Cache-Control Cheatsheet 2026

Updated April 2026

Quick Answer Cache-Control is an HTTP header that tells browsers and CDNs how long to cache a response. Key values: 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

DirectiveWho respects itWhat it doesWhen to use
max-age=NBrowser + CDNFresh for N secondsAll cacheable resources
s-maxage=NCDN onlyCDN-specific TTL, overrides max-ageDifferent CDN vs browser TTL
no-cacheBrowser + CDNStore but revalidate before every serveHTML pages, frequently updated resources
no-storeBrowser + CDNDo not store at allAuthenticated responses, sensitive data
privateCDN onlyBrowser can cache, CDN cannotUser-specific responses
publicCDNCDN allowed to cacheStatic assets, public API responses
immutableBrowserNever revalidate during max-ageFingerprinted/hashed static assets
stale-while-revalidate=NBrowser + CDNServe stale for N seconds while fetching fresh in backgroundAPIs, pages where some staleness is OK
stale-if-error=NBrowser + CDNServe stale if origin returns error for N secondsHigh-availability sites
must-revalidateBrowser + CDNMust check origin once stale — no serving stale on errorPricing, 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

Visualise any Cache-Control header → Cache-Control Simulator