WordPress

Cloudflare full-page caching for WordPress & WooCommerce: the complete setup

July 2026 · 12 min read · ← All posts

Put Cloudflare in front of a WordPress site and your images, CSS and JavaScript get cached at the edge — but your HTML doesn't. Every page view still travels to your origin, boots PHP, runs every plugin hook and queries MySQL. Run any WordPress URL through our cache checker and you'll almost certainly see cf-cache-status: DYNAMIC — Cloudflare's way of saying "I'm not even trying to cache this."

This guide sets up full-page (HTML) caching with Cloudflare Cache Rules, properly: the exact bypass expressions for WordPress and WooCommerce, the rule order that most guides get backwards, and the part almost everyone skips — what Edge TTL and Browser TTL actually control, and why mixing them up means stale pages you can't purge.

First, understand the two caches you're configuring

A Cache Rule sets two separate lifetimes, and they live in completely different places:

Edge cache (Edge TTL)Browser cache (Browser TTL)
LivesIn Cloudflare's 300+ data centersOn each visitor's device
ServesEvery visitor near that locationOnly that one visitor, on repeat views
You can purge itYes — one click or API callNo — it expires when it expires
Sane value for HTMLHours to days (aggressive)Minutes, or 0 (conservative)

This asymmetry is the whole strategy: be aggressive at the edge, conservative in the browser. You can purge Cloudflare's cache the moment you publish an update — so a long Edge TTL costs you nothing in freshness. You cannot reach into a visitor's browser: if you let browsers cache your HTML for a day, they'll show yesterday's page for a day, no matter how often you purge Cloudflare. Long TTLs belong to fingerprinted assets (app.a3f9c2.css) — never to HTML.

The setup: two Cache Rules

In the Cloudflare dashboard, go to Caching → Cache Rules. We'll create two rules — one that makes everything cacheable, one that carves out what must never be cached.

Rule 1: cache everything on the domain

Expression:

(http.host eq "example.com")

Settings:

Why override the origin header for the edge but respect it for the browser? Because WordPress's default Cache-Control says "don't cache me" — correct for browsers you can't purge, wasteful for an edge you can. The Edge TTL override lets Cloudflare cache what the origin header forbids, without changing what browsers are told.

Rule 2: bypass everything dynamic (this rule must come after Rule 1)

Expression — WordPress core plus WooCommerce:

(starts_with(http.request.uri.path, "/wp-admin"))
or (http.request.uri.path contains "/wp-login.php")
or (http.request.uri.path contains "/cart")
or (http.request.uri.path contains "/checkout")
or (http.request.uri.path contains "/my-account")
or (http.request.uri.query contains "wc-ajax=")
or (http.cookie contains "wordpress_logged_in_")
or (http.cookie contains "wordpress_sec_")
or (http.cookie contains "wp_woocommerce_session_")
or (http.cookie contains "woocommerce_items_in_cart")
or (http.cookie contains "woocommerce_cart_hash")
or (http.cookie contains "comment_author_")
or (http.cookie contains "PHPSESSID")

Setting: Cache eligibility → Bypass cache.

What each group does:

The rule-order mistake half the internet makes: Cloudflare evaluates Cache Rules top to bottom, and when rules conflict, the last matching rule wins — it is not "first match, then stop." Your bypass rule therefore belongs after the cache-everything rule, so it overrides it. Guides that tell you "bypass first, cache-everything second" have it exactly backwards: in that order, the cache-everything rule matches last and wins, and your checkout page ends up cached. If you only remember one thing from this article, make it this.

Optional Rule 3: long TTLs for feeds and sitemaps

Feeds and XML sitemaps are generated by PHP too, and bots hammer them. They're safe to cache — just keep the TTL short enough that new posts show up (an hour is plenty). If you use a dynamic sitemap plugin that must always be fresh, bypass it instead; Servebolt's variant of this guide excludes .xml and .txt outright, which is the cautious default if you don't want to think about it.

The Set-Cookie trap (why your cache never gets warm)

By default, Cloudflare refuses to cache any response that carries a Set-Cookie header. A surprising number of WordPress themes and plugins start a session or set a cookie on every page view — analytics plugins, GeoIP plugins, some page builders, WooCommerce configured to start sessions early. Result: every response has Set-Cookie, nothing is ever stored, and your hit ratio sits at zero even though your rules are correct.

Two things to know:

Verify it — properly, from more than one place

Test the full matrix, because each state exercises a different rule:

RequestExpected cf-cache-status
Product/blog page, incognito, 1st requestMISS (edge stored it)
Same page, 2nd requestHIT
Same page, with an item in the cartBYPASS (cookie rule)
/checkout/, /cart/, /my-account/BYPASS
Logged in, any pageBYPASS

Full decoder for every status value in our cf-cache-status guide. And remember that a HIT from your desk only proves your nearest Cloudflare location is warm — each PoP caches independently. The multi-location cache checker shows the status from several continents at once, and the website speed test shows what the first vs. repeat visit actually costs per region.

Purging: wire it up before you raise the TTL

Full-page caching without purge-on-publish means editors see stale pages and panic. Set it up on day one:

Cache Rules vs. APO — which one?

Cloudflare's Automatic Platform Optimization (APO) is the managed alternative: a Workers-based service ($5/month on the Free plan, included from Pro up) that caches WordPress HTML with built-in WordPress/WooCommerce cookie logic and purges via the official plugin on publish. Its standout feature: pages are replicated to Workers KV within about a minute, globally — one visit anywhere seeds every location, whereas a Cache Rule only fills the one PoP that got the request.

Cache Rules (this guide)APO
CostFree, all plans$5/mo on Free, included Pro+
ControlFull — your expressionsBlack box, WordPress-aware
WooCommerce exclusionsYou maintain themBuilt in
Cold-start behaviorPer-PoP: each location warms separatelyKV replication softens it
Non-WordPress paths on same zoneHandled by your rulesNot covered

Never stack both. If APO is on, a manual cache-everything rule overrides APO's cookie logic and reintroduces the cached-cart problem APO exists to prevent. Pick one approach.

After the setup: the cache is configured, not warm

Everything above makes your HTML cacheable. It doesn't put a single page into a single cache — that still takes a request per URL, per edge location. After every purge and every TTL expiry, all of it starts cold again, and eviction quietly empties low-traffic locations even sooner. Your page-cache plugin's preload can't help here: it warms your server, not Cloudflare's 300+ locations.

That last mile is what warmup.rocks does: it reads your sitemap and requests every URL through proxies around the world on a schedule, so each edge location holds its own warm copy — with per-location hit-ratio reporting so you can see it working.

Does Cloudflare cache WordPress HTML by default?

No. By default Cloudflare caches static assets (images, CSS, JavaScript) based on file extension, but never HTML — every page view goes to your origin and shows cf-cache-status: DYNAMIC. You must opt in with a Cache Rule set to "Eligible for cache" (or use APO).

What's the difference between Edge TTL and Browser TTL?

Edge TTL controls how long Cloudflare's data centers keep a copy; you can purge it anytime, so it can be long. Browser TTL controls how long a visitor's browser keeps a copy; you can never purge it, so for HTML it should be minutes or zero. Purging Cloudflare does not touch what's already in visitors' browsers.

Why is my checkout page cached even though I created a bypass rule?

Almost always rule order. Cloudflare combines matching Cache Rules and conflicting settings take the value of the last matching rule. If your bypass rule sits above the cache-everything rule, the cache-everything rule matches last and wins. Move the bypass rule below it and purge everything once.

Is it safe to cache HTML for a WooCommerce store?

Yes — for the pages that are identical for every anonymous visitor: products, categories, content. Cart, checkout and account pages must bypass, and so must any request carrying WooCommerce session cookies (wp_woocommerce_session_, woocommerce_items_in_cart, woocommerce_cart_hash). The mini-cart stays live because WooCommerce fetches it via wc-ajax fragments, which your rules exclude from caching.

Should I use APO or Cache Rules?

APO if you want managed, WordPress-aware caching with global KV replication and don't need custom control. Cache Rules if you want full control over expressions and TTLs, run non-WordPress paths on the same zone, or don't want the $5/month on the Free plan. Never run both together.

Configured the cache? Now keep it warm.

warmup.rocks reads your sitemap and keeps your WordPress HTML warm across Cloudflare's edge locations — with per-location hit-ratio reporting after every run. See how our WordPress cache warmer works.

Start your 7-day free trial