D
P
0

WordPress & PHP

Deployed + Cleared WP Rocket but the Public Still Sees the Old Version? Cloudflare Is Caching HTML for a Year

June 25, 2026·5 min read
Deployed + Cleared WP Rocket but the Public Still Sees the Old Version? Cloudflare Is Caching HTML for a Year

I had just deployed a change to a multi-listing site I built, then did the usual: clicked "Clear Cache" in WP Rocket. I opened the site as an admin and everything was fresh — new copy, new layout, all correct. Then the client messaged me: "Still the old version." I opened an incognito window, and sure enough, there it was — the old page, exactly as it looked before the deploy. Not stale by a second or two. Stale for a long time. Days, if I'd left it alone.

What tipped me off immediately: as an admin I saw the new version, but the public saw the old one. That's a very specific pattern. I checked the response headers with curl from a machine that wasn't logged in:

curl -sI https://old-site.com/listing/ | grep -i cf-cache
cf-cache-status: HIT

HIT. That settled it: the response the public was getting wasn't coming from my WordPress origin at all. It was coming from Cloudflare's edge.

Why this happens

There was a Cloudflare Page Rule set to Cache Everything with a very long TTL — the Edge Cache TTL was dialed up to the year range. That means Cloudflare wasn't just caching static assets like images, CSS, and JS (which it should), it was caching the full HTML of every page and holding it at its edge nodes for an extremely long time.

Cloudflare's default behavior is actually polite: it won't cache HTML unless you tell it to. The moment you add a "Cache Everything" rule, those rules change completely. Cloudflare starts treating dynamic HTML pages like static files. Once a URL is stuck at the edge with a HIT, the origin never gets touched again until the TTL expires or the cache is purged.

And this is exactly why I fooled myself as an admin. When I'm logged into WordPress, my browser sends the WordPress login cookie. Cloudflare has a default rule to bypass cache when certain cookies are present — including the WP login session cookie. So my admin requests always punched straight through to the origin, hit the WP Rocket cache I'd just cleared, and served fresh HTML. Meanwhile, public visitors with no such cookie were served entirely from the stale copy at the edge.

The crucial thing to internalize: clearing WP Rocket empties the cache at the origin. WP Rocket lives on the WordPress server. But the stale copy the public was seeing didn't live at the origin — it lived at Cloudflare. So when I clicked "Clear Cache" in WP Rocket, I was tidying a room that was already clean and was never the source of the problem. Cloudflare out in front knew nothing about it and kept calmly serving its old HTML.

Here's how I visualize the cache chain:

Public visitor -> Cloudflare edge (stale HTML, HIT) -> [never reaches] -> Origin (WP + WP Rocket, fresh)
Admin (has WP cookie) -> [bypasses edge] -> Origin (WP + WP Rocket, fresh)

My origin was correct. My WP Rocket was correct. What was stale was a layer I didn't even realize was caching HTML at all.

The fix

The immediate fix so the client could see the new version right away: run Purge Everything in Cloudflare. Caching → Configuration → Purge Everything. Once the edge cache is dumped, the next public request is forced back to the origin, pulls fresh HTML, and Cloudflare then caches that new version. Incognito instantly showed the latest build.

But "manually Purge Everything on every deploy" is fragile — sooner or later someone forgets. So I made it part of the deploy process. Cloudflare has a purge API, and I call it at the end of every deploy that changes HTML:

# Called at the end of the deploy
curl -sS -X POST \
  "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"purge_everything":true}'

When only a few pages change, it's cheaper to purge specific URLs than to dump the whole cache:

curl -sS -X POST \
  "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/purge_cache" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  --data '{"files":["https://old-site.com/","https://old-site.com/listing/"]}'

The more fundamental fix is to stop edge-caching HTML for dynamic responses. For a WordPress site whose content changes often, "Cache Everything" on HTML does more harm than good. I adjusted the Page Rule: let static assets (images, CSS, JS) keep their long edge cache — that's exactly what you want — but don't force HTML into the edge cache, or at least give it a sane Edge Cache TTL (minutes, not years) instead of a year-long one. That way dynamic pages stay fresh without constant manual purges, while WP Rocket keeps handling caching at the origin, which is its actual job.

The takeaway

Once there's a CDN in front of WordPress, purging the cache at the origin is only half the job. WP Rocket clears the origin; Cloudflare holds its own copy at the edge that doesn't care in the slightest whether you cleared WP Rocket or not. If your edge caches full HTML, you must purge the edge too on every deploy — otherwise visitors keep seeing yesterday's site while your own dashboard looks perfect.

And if you ever hit the "admin fresh, public stale" symptom, suspect a cache layer that gets bypassed by a login cookie right away. Check cf-cache-status with curl from a machine that isn't logged in. The moment you see a HIT on a page that should be dynamic, you've found your culprit — and you know exactly where the purge needs to happen.