Performance

Fix Cache-Control Headers โ€” Why Your CDN Isn't Caching

Updated April 2026

Reading this article? Verify your fix in real-time. Run a live PageSpeed audit โ€” SpeedFixer โ†’

If Cache-Control is missing, CDNs default to not caching โ€” or make inconsistent decisions based on other headers. Explicit Cache-Control headers give you full control over what gets cached and for how long.

Check your current headers first

curl -I https://yoursite.com/
curl -I https://yoursite.com/static/app.js
curl -I https://yoursite.com/api/data

If you see no Cache-Control or Cache-Control: no-cache on static assets, your CDN is not caching them โ€” every request goes to your origin server.

Cache-Control by resource type

Versioned static assets (JS, CSS, fonts)

These have content hashes in the filename (app.abc123.js). The content never changes, so cache them forever:

Cache-Control: public, max-age=31536000, immutable

immutable tells browsers not to revalidate during max-age, even on back/forward navigation. Removes unnecessary conditional requests.

Non-versioned static assets (images, logo.png)

Cache for a week or month, but allow revalidation:

Cache-Control: public, max-age=604800, stale-while-revalidate=86400

HTML pages

Cache briefly or not at all โ€” HTML references your versioned assets, so you need users to get fresh HTML when you deploy:

# Option 1 โ€” no CDN cache (always fresh)
Cache-Control: public, max-age=0, must-revalidate

# Option 2 โ€” CDN cache with instant invalidation
Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400

API responses (non-authenticated)

# Cache for 60 seconds at CDN, serve stale for 24h while revalidating
Cache-Control: public, s-maxage=60, stale-while-revalidate=86400

API responses (authenticated)

# Never cache at CDN โ€” must use private
Cache-Control: private, no-store

Config by stack

Nginx

location ~* \.(js|css|woff2|png|jpg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable" always;
}

location ~* \.html$ { add_header Cache-Control "public, max-age=0, must-revalidate" always;
}

Vercel (vercel.json)

{ "headers": [ { "source": "/static/(.*)", "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] }, { "source": "/(.*\.html)", "headers": [{ "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }] } ]
}

Cloudflare

Cloudflare Dashboard โ†’ Caching โ†’ Cache Rules โ†’ Create rule โ†’ File extension matches js,css,png,jpg,woff2 โ†’ Edge Cache TTL: 1 year.

Use EdgeFix to audit your current caching headers and see exactly what the CDN is receiving for each resource type.

Run a live PageSpeed audit โ†’ SpeedFixer
Visualise your Cache-Control header โ†’ Cache-Control Simulator