You update a page, hit publish, clear your browser cache just to be safe — and the old version is still there. Sometimes it's just you; sometimes support tickets start rolling in because customers are seeing yesterday's pricing or a fixed typo that "isn't fixed." If your domain sits behind Cloudflare's orange cloud, this is almost always a caching problem, not a deployment problem. Here's how to find which cache is lying to you and clear it properly.
Symptom: The Change Is Live on the Server, But Not in the Browser
You can confirm the file changed — you edited the page, or `git log` shows the commit, or the database row has the new value. But loading the site in a browser (or a customer telling you what they see) shows the old content. Sometimes a hard refresh (Ctrl+Shift+R) fixes it instantly. Sometimes it doesn't, even in incognito mode, even from a phone on mobile data. That difference matters — it tells you which layer is stuck.
Cause 1: Cloudflare's Edge Cache Still Has the Old Copy
Cloudflare caches static assets by default (CSS, JS, images) and, depending on your Cache Level and Page Rules, sometimes full HTML too. Every one of Cloudflare's edge data centers can be holding its own cached copy of your page. A visitor in Mumbai might get a fresh version while one in Singapore is still served the old one, because each PoP (point of presence) caches independently until its TTL expires or you purge it.
This is the single most common cause when incognito mode also shows stale content — if a fresh, un-cached browser session still sees the old page, the browser isn't the problem.
Cause 2: The Visitor's Own Browser Cache
If a hard refresh fixes it for you but regular users still complain, it's browser-side caching — usually driven by `Cache-Control` or `Expires` headers your origin server (or a plugin) is sending. Chrome and Firefox will happily serve HTML from disk cache for hours if the headers tell them to, no request to Cloudflare or your server involved at all.
Cause 3: A Server-Side Caching Plugin or LiteSpeed Cache
If you're on WordPress with LiteSpeed Cache, WP Super Cache, W3 Total Cache, or a similar plugin, there's a third layer sitting between your database and Cloudflare: a static HTML snapshot saved on disk. Cloudflare might be forwarding requests correctly, but if it's fetching from a plugin cache that hasn't regenerated yet, you'll see the same stale result. This is easy to miss because people usually check "did I purge Cloudflare?" and stop there.
Cause 4: Page Rules or "Always Online" Overriding Origin Behavior
A Page Rule with Cache Level set to "Cache Everything" will cache full HTML responses at the edge regardless of what your server's headers say — including logged-in admin pages if you're not careful with bypass rules. Cloudflare's "Always Online" feature is a separate trap: it serves an archived version of your page from the Wayback Machine crawl when it thinks your origin is down, which can look identical to a stale-cache issue but has a completely different fix (it resolves itself once Cloudflare confirms your origin is healthy).
Fix: Purge in the Right Order
Work from the layer closest to your server outward — purge the plugin cache first, then Cloudflare, then verify. Purging Cloudflare before the plugin cache just means Cloudflare re-caches the same stale HTML a few seconds later.
- Purge the server-side cache first. In LiteSpeed Cache: LSCache > Toolbox > Purge All. In WP Super Cache or W3TC: use the plugin's "Delete Cache" / "Empty All Caches" button. If you're using object caching (Redis/Memcached) alongside a page cache, flush both.
- Purge Cloudflare's cache. In the dashboard: Caching > Configuration > Purge Everything (or purge by URL if you only changed one page, which is gentler on performance for high-traffic sites). Via API, for a single URL:
curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/purge_cache" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"files":["https://example.com/pricing/"]}'
To purge everything on the zone instead of one URL:
curl -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/purge_cache" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
- Test from a clean state. Use a private/incognito window, or better, a tool like
curl -Iagainst the URL to inspect headers directly without any local browser cache involved:
curl -I https://example.com/pricing/
Look at the cf-cache-status response header. HIT means Cloudflare served from cache; MISS or EXPIRED means it went to your origin and got a fresh copy. If you just purged and still see HIT, the purge hasn't propagated to that PoP yet — give it 30 seconds and retry.
- If it's still stale after both purges, check for a Page Rule caching that specific path longer than expected, or "Always Online" masking an origin issue — temporarily disable it under Reliability settings and reload.
Prevention: Set Cache-Control Headers Deliberately
Purging manually after every change doesn't scale. A better long-term fix is telling Cloudflare and browsers exactly how long each type of content should be cached, so dynamic pages update instantly and static assets still get the performance benefit.
| Content type | Recommended Cache-Control | Why |
|---|---|---|
| HTML pages (blog posts, products) | no-cache or short TTL (5–10 min) | Content changes; you want fast revalidation, not long edge storage |
| CSS/JS with versioned filenames | max-age=31536000, immutable | Filename changes on every deploy, so long caching is safe |
| Images/fonts (rarely change) | max-age=2592000 (30 days) | Balances freshness with fewer origin requests |
| Admin/logged-in pages | private, no-store | Must never be cached at the edge or shared by other visitors |
If you're on cPanel/Apache, these go in .htaccess via mod_expires and mod_headers. On Nginx, set them in the `location` block for each asset type. Either way, once headers are correct, Cloudflare respects them automatically and you'll rarely need a manual purge again — except right after intentional content updates, which is exactly what the "Purge by URL" option is for.
Frequently Asked Questions
Why does the page look fresh in Chrome but old in Firefox (or vice versa)?
Each browser maintains its own local cache independent of the other. If Cloudflare has already served fresh content, a browser showing old content is purely local — clear that browser's cache or do a hard refresh (Ctrl+Shift+R / Cmd+Shift+R).
Does "Purge Everything" in Cloudflare hurt my site's performance?
Briefly, yes — every asset has to be re-fetched from your origin and re-cached at each edge location on the next request, so you'll see a short spike in origin load and slightly slower first-loads right after a full purge. For a single content change, purge by URL instead; save "Purge Everything" for site-wide changes like a theme or CSS overhaul.
I purged Cloudflare and the plugin cache, but it's still stale. What else could it be?
Check for a CDN or caching layer you forgot about — some managed WordPress setups run a second reverse proxy (Varnish, Nginx FastCGI cache) in front of PHP-FPM. Also check whether a Cloudflare Worker or Page Rule is caching that specific path with a custom Edge Cache TTL that ignores your origin headers.
How do I know if Cloudflare is serving my page from cache without checking the dashboard?
Run curl -I https://yourdomain.com/path/ and look at the cf-cache-status header — values are HIT, MISS, EXPIRED, BYPASS, or DYNAMIC. This works from any terminal without touching your browser cache at all.
Should I turn off Cloudflare caching entirely to avoid this problem?
No — you'd lose most of the performance and DDoS-absorption benefit Cloudflare provides. Instead, set accurate Cache-Control headers and use "Cache Everything" Page Rules only on paths you're sure are safe (never on logged-in areas), so the cache does its job without fighting your content updates.
