Cache-Control Header Generator

Tell it what you're caching — get the header right.

1 · Caching intent

2 · Freshness lifetime

3 · Scope

4 · Extra directives

Generated header

Preset library

Directive explainer

public / private / no-store
Who may cache: everyone, the browser only, or nobody. Personalized responses must never be public.
max-age=N
Freshness lifetime in seconds. After it expires, caches must revalidate before reuse.
immutable
Promises the body never changes — browsers skip even force-reload revalidation. Only for hashed filenames.
must-revalidate
Stale copies must be revalidated with the origin before serving. Use where staleness has consequences.
stale-while-revalidate=N
Serve stale instantly while refreshing in the background — users never wait.
s-maxage=N
Overrides max-age for shared (CDN) caches only — different lifetimes for edge vs browser.

CDN notes

  • Query strings create separate cache objects — good for busting, bad with random params.
  • Set Vary: Accept-Encoding when serving compressed responses.
  • Prefer long TTL + purge-on-deploy over short TTLs.
  • Strip cookies from the cache key for public assets or hit rates collapse.

What the Cache-Control Header Does

Every HTTP response can carry a Cache-Control header, and it is the single most important line in web performance. It tells browsers, CDNs, and proxies three things: whether the response may be stored, where it may be stored, and how long it stays fresh. Get it right and repeat visits load in milliseconds from a cache near the user; get it wrong and users see stale prices, broken deploys, or — just as bad — nothing cached at all and a slow site.

The header is a comma-separated list of directives. The two you will use most are the scope (public, private, or no-store) and the freshness lifetime (max-age=N, in seconds). This tool's core function builds exactly that pair — cacheHeader(3600, 'public') produces public, max-age=3600 — and the UI layers on the real-world directives professionals add around it.

Public vs Private vs No-Store

The scope directive answers one question: who is allowed to keep a copy?

The classic bug is serving personalized HTML with public: one user's dashboard gets cached at the CDN and served to the next visitor. The classic opposite bug is serving static assets with private or no header at all, forcing every user to re-download your 2MB JavaScript bundle on every visit.

Max-Age: How Long Is Fresh?

Content typeTypical max-ageReasoning
Hashed static assets (app.a3f9c2.js)31536000 (1 year) + immutableFilename changes when content changes — can never go stale
Images, fonts604800–2592000 (1 week–30 days)Rarely change; long cache with versioned URLs
HTML pages0–3600Must stay fresh; often no-cache with revalidation
API responses (public data)60–3600Balance freshness vs load; use ETags for revalidation
API responses (personalized)0, privateFresh per user; browser-only caching at most

The golden rule of cache lifetimes: cache the immutable aggressively, keep the entry points fresh. Your fingerprinted assets (filenames containing a content hash) can be cached for a year with immutable because a changed file gets a new name — the old cached copy can never be wrong. Your HTML, which references those filenames, must be revalidated quickly so users pick up the new references. This two-tier strategy is how modern frameworks (Next.js, Vite, Create React App) achieve both instant repeat loads and safe deploys.

The Directives That Matter (Explainer)

must-revalidate

Once the cached copy goes stale, the cache must check with the origin server before serving it. Without this, some caches are allowed to serve stale content in edge cases (like when the origin is unreachable). Add it to anything where staleness has consequences — prices, inventory, account state.

immutable

A promise: this response body will never change during its freshness lifetime. Browsers take it seriously — Firefox and Chrome will not even revalidate on a force-reload for immutable resources. Only use it with content-hashed filenames; applying it to a mutable URL is how you ship a bug that users cannot clear.

stale-while-revalidate=N

The performance secret weapon: serve the stale copy immediately while fetching a fresh one in the background for next time. Users never wait; content stays reasonably fresh. Excellent for semi-static content like blog posts, docs, and marketing pages. Pair with a modest max-age: fresh for an hour, usable-while-revalidating for a day.

no-transform

Forbids intermediate caches from modifying the body — relevant when carriers or proxies compress or transcode images. Matters for image-heavy sites behind aggressive middleboxes.

s-maxage=N

Overrides max-age only for shared caches (CDNs). Lets you tell the CDN 'keep it for a day' while telling browsers 'keep it for an hour' — fine-grained control over the two cache layers.

CDN Notes: What Changes at the Edge

A CDN is a shared cache with opinions. Key things to know:

Validation: ETags and Last-Modified

Freshness (max-age) is only half the story; validation is the other half. When a cached copy goes stale, the client can ask 'has it changed?' instead of re-downloading: the server answers with an ETag (a content fingerprint) or Last-Modified timestamp. If nothing changed, the server replies 304 Not Modified with an empty body — all the latency savings of caching with none of the staleness risk. The winning combination for HTML and APIs is short max-age (or no-cache) + ETag: always check, rarely download.

Common Cache-Control Mistakes

  1. Caching HTML for a year. Users get stuck on old deploys. HTML should be fresh; assets should be long-lived.
  2. Using no-cache when you mean no-store. no-cache still stores — it just revalidates. For truly sensitive data, no-store.
  3. Forgetting immutable needs hashed filenames. Immutable on a mutable URL is a footgun.
  4. Private data served as public. Audit any response containing user data — one public on a dashboard endpoint is a breach.
  5. No header at all. Without Cache-Control, browsers fall back to heuristic caching (often 10% of time since Last-Modified) — unpredictable and usually too short for assets, too long for HTML.

How to Verify Your Headers

Generate the header here, set it, then verify in the real world: open DevTools → Network tab → click a request → check the cache-control response header and the Size column ('memory cache' / 'disk cache' means it worked). For CDN behavior, check the age or x-cache headers your provider adds. Tools like WebPageTest and Google's PageSpeed Insights will flag missing or misconfigured caching as one of the highest-impact fixes — because it usually is.

Framework Defaults: What You Get Out of the Box

Modern frameworks have opinions about caching, and knowing them saves debugging. Next.js serves _next/static assets with public, max-age=31536000, immutable (hashed filenames — the gold standard) while pages default to short-lived or revalidated responses. Vite emits hashed asset filenames designed for the same year-long immutable treatment; you add the header in your server or CDN config. Nginx ships no Cache-Control by default — you write the add_header lines yourself, which is why generated snippets like this tool's nginx copy button exist. Cloudflare, Fastly, and AWS CloudFront respect your origin's Cache-Control but each add their own edge-TTL settings that can override it — always check whose TTL actually wins in your CDN's dashboard, because an edge rule of 'respect origin' versus 'override to 1 hour' changes everything.

The single most common production bug in this space is the hashed-asset HTML mismatch: assets cached for a year (correct) while the HTML referencing them is also cached for a day or more (incorrect), so deploys do not reach users until the HTML expires. The fix is always the same two-tier setup: immutable year-long caching for fingerprinted assets, and no-cache + ETag (or very short max-age) for HTML entry points.

Cache-Control and Privacy Regulations

Caching intersects with privacy law in ways developers underestimate. A public directive on a response containing personal data is not just a bug — under GDPR-style regimes it can be a reportable incident if a shared cache serves one user's data to another. Audit every endpoint that returns user-specific content and ensure it carries private or no-store; better yet, set these at the framework middleware level so no endpoint can forget. Cookie-based sessions add another trap: some CDNs include cookies in the cache key by default, silently personalizing every cached object and destroying hit rates. The correct setup strips cookies from cache keys for public assets and bypasses the cache entirely for authenticated HTML. When in doubt, private, max-age=0, must-revalidate is the conservative default for anything behind a login.

Debugging Cache Issues: A Checklist

  1. Check what was actually sent: DevTools → Network → response headers. Trust this over your config files.
  2. Check the Size column: 'memory cache' or 'disk cache' means the browser served it locally; a 200 with full download means it did not.
  3. Disable cache and compare: DevTools' 'Disable cache' checkbox isolates caching from application bugs.
  4. Look for the CDN's headers: age, x-cache: HIT/MISS, or cf-cache-status reveal the edge layer's decision.
  5. Test with curl: curl -sI -H 'Cache-Control: no-cache' URL shows origin headers without browser interference.
  6. Verify purge worked: after purging, confirm MISS on first request and HIT on second — a purge that 'succeeds' but leaves stale copies is worse than no purge.

Frequently asked questions

What is the Cache-Control header?

Cache-Control is an HTTP response header that tells browsers, CDNs, and proxies whether a response may be cached, where, and for how long. It is the main control for web caching and one of the highest-impact web performance settings.

What is the difference between public, private, and no-store?

public allows any cache (browser, CDN, proxies) to store the response — for identical-for-everyone assets. private allows only the end user's browser — for personalized content. no-store forbids all caching — for sensitive data.

How do I choose a max-age value?

max-age is the freshness lifetime in seconds: 3600 = 1 hour, 86400 = 1 day, 31536000 = 1 year. After it expires, the cache must revalidate before reuse. This tool converts hours/days to seconds automatically.

When should I use the immutable directive?

immutable promises the body will never change during its lifetime, so browsers skip even force-reload revalidation. Only use it with content-hashed filenames (app.a3f9c2.js) where a changed file gets a new name.

Is no-cache the same as no-store?

No — no-cache means 'store it but always revalidate before reuse.' no-store means 'never store it at all.' For sensitive data like banking or health responses, you want no-store.

How do I apply a generated header to my site?

Copy the generated header from this tool, set it in your server or framework config (e.g., nginx add_header, Express res.set, or your CDN's rules), then verify in DevTools → Network: the response should show your cache-control value and repeat loads should come from cache.