Fix Cache-Control Headers — Why Your CDN Isn't Caching
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