← Blog

CLOUDFLARE CACHE TRAPS (AND HOW WE HIT ONE)

A real incident from our own site: an immutable cache header, a forgotten version bump, and how to tell CDN cache apart from server-side template cache.

Cloudflare Cache Traps (and How We Hit One)

Last week we made a small visual update on our own site, uploaded it via FTP, refreshed the page — and still saw the old version. The file on the server was correct. It wasn't browser cache either. The culprit was Cloudflare itself, and plenty of people have fallen into this same trap before.

The hidden cost of the immutable cache header

While pushing our PageSpeed score to 100, we added an aggressive cache header to static files (CSS, JS):

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

This means "this file will never change for a year, don't bother asking again" — great for performance. Paired with a version query like ?v=4 (the "cache-busting" pattern), the actual contract is: bump the version number whenever the file changes, and app.css?v=5 counts as a brand-new URL that never touches the old cache.

The problem: we updated the CSS but forgot to bump the ?v= number. Because we'd promised Cloudflare that app.css?v=4 would never change for a year, it never even asked our server for the new file — it kept serving the old one straight from its own edge cache.

Why "clear your browser cache" didn't work

Because the problem wasn't in the browser — it was on Cloudflare's edge servers. The user's browser asks Cloudflare, Cloudflare says "got it right here" — never touching our server at all. The cf-cache-status: HIT header gives it away; a quick curl -sI makes it obvious immediately.

The fix — and the rule

Every time app.css (or any other versioned static file) changes, bumping the version number in the HTML reference is mandatory:

<link rel="stylesheet" href="/assets/css/app.css?v=5">

Don't forget to deploy the template file containing that line too — upload only the CSS and forget the HTML, and the browser will still request the old ?v=4 URL.

In an emergency (if the version bump was missed), you can manually purge a specific URL or the entire cache via Cloudflare Dashboard → Caching → Configuration → Purge Cache — but that's a one-time rescue, not a permanent fix.

A separate issue easily confused with this one: Twig template cache

There's a second layer in the same session: server-side, Twig stores compiled templates under var/twig-cache/. Update and deploy an HTML template, and if auto_reload is off, Twig keeps using the old compiled version. The fix here is completely different — not a Cloudflare purge, but deleting the var/twig-cache/ directory on the server.

The two layers look similar but live in different places: one on the CDN (Cloudflare), the other on the server (Twig). If a change isn't showing up live, figure out which layer it's stuck in first — checking the cf-cache-status header with curl -sI is the fastest way to tell.

Takeaway

Aggressive cache headers are the right call for performance — but "right" and "forgetting-proof" aren't the same thing. Turning the version bump into a written rule on your deploy checklist is the only reliable way to never fall into this trap again.

Running into a similar cache mess on your own site? Get in touch — we can usually diagnose it in 5 minutes with curl -sI.

Want to know more about this?

If you'd like to bring the approach in this article to your own infrastructure, or just want to talk through a similar need, get in touch with our team.

Contact us →