Cloudflare#
Version 0.3.0 adds two independent features in inc/class-cloudflare.php:
- Purge mirror. Every page-cache purge is repeated at the Cloudflare zone.
- Edge headers. A
Cache-Controlcontract that lets Cloudflare hold public HTML and keeps everything else out of the edge cache.
You can use the purge mirror without edge headers (for example to clear a zone that caches HTML through its own rules), and edge headers need no credentials. Used together, Cloudflare serves public pages from the edge and drops them when the content changes.
The only Cloudflare host the plugin calls is api.cloudflare.com.
Settings#
On Settings → BB Cache → Cloudflare:
| Key | Default | What it does |
|---|---|---|
cf_purge |
0 |
Turns the purge mirror on. It only acts when a valid zone ID and token are available. |
cf_zone |
empty | Zone ID: 32 hexadecimal characters, from the zone's Overview page in the Cloudflare dashboard. |
cf_token |
empty | API token. See Token permission. |
cf_purge_mode |
urls |
urls (Changed URLs) or all (Everything). See What gets purged and when. |
cf_edge_ttl |
0 |
Seconds the edge may hold public HTML. 0 sends no edge headers, so Cloudflare keeps treating HTML as dynamic. 900 lets the edge hold public pages for 15 minutes. |
A zone ID that is not 32 hexadecimal characters, or a token that is not at least 20 characters of letters, digits,
_ and -, counts as missing.
Constants#
Both credentials can be defined in wp-config.php instead of the settings:
define( 'BB_CACHE_CF_ZONE', '<zone id>' );
define( 'BB_CACHE_CF_TOKEN', '<api token>' );
A defined constant always wins over the setting, and the field on the settings screen is replaced by a disabled "Defined in wp-config.php" note.
The constant is the preferred place for the token:
- The token then never sits in the database, so it does not travel with a database export, a backup of the options table or a database push to another environment.
- A token saved in the settings is stored in plain text in the
bb_cache_settingsoption and is printed into the settings form (a password field on the Cloudflare tab, a hidden field on the other tabs), where any administrator can read it from the page source. - Each environment can carry its own token, or none.
Token permission#
Create the token in the Cloudflare dashboard under My Profile → API Tokens → Create Token with a single permission, limited to the one zone:
- Zone > Cache Purge > Purge
Optionally add Zone > Zone > Read. It is not needed for purging; it only lets Verify token show the zone name and plan.
What gets purged and when#
The mirror listens to the bb_cache_purged action, which the page cache fires after every purge: a post save, the
admin bar, the Tools tab, WP-CLI, or another plugin calling bb_cache_purge_all or bb_cache_purge_url.
| Page cache event | urls mode |
all mode |
|---|---|---|
| One URL purged | The URL is queued | Purge everything is queued |
| A post purged | Its URLs are queued one by one: permalink, home page, archives, parent | Purge everything is queued |
| Everything purged | Purge everything is queued | Purge everything is queued |
Purges are queued during the request and sent once, on shutdown:
- If a purge-everything is queued, one
purge_everythingcall is sent and the queued URLs are dropped. - Otherwise the queued URLs are sent to the zone's
purge_cacheendpoint asfiles, 30 URLs per API call. - Each call is blocking with a 10 second timeout and writes one entry to the purge log.
Under WP-CLI, wp bb-cache purge sends the queue immediately and prints the outcome.
Since 0.3.0 the page cache fires its purge event for a URL even when no file was stored for it. The edge may still hold a URL that expired or was purged locally, and it must be purged all the same.
Things to know about urls mode:
- A URL purge at Cloudflare is exact: scheme, host, path and query string must match what the edge stored.
- When the page cache purges a post's permalink it also removes everything stored beneath that path (paged and child URLs). The mirror receives the permalink only, so those deeper URLs stay at the edge until the edge TTL runs out.
- Only strings that look like
http://orhttps://URLs are sent.
all mode avoids both limits at the cost of emptying the zone's cache, assets included, on every content change.
The Cache-Control contract#
With cf_edge_ttl greater than 0, every front-end HTML response gets one of two headers:
| Response | Header |
|---|---|
Served from the page cache (drop-in or .htaccess block), or about to be stored in it |
Cache-Control: public, max-age=0, s-maxage=<cf_edge_ttl> |
| Any other front-end HTML: logged in, preview, 404, search, session cookie, unknown query string | Cache-Control: private, no-cache, must-revalidate, max-age=0 |
max-age=0 makes browsers revalidate on every visit (the drop-in answers with 304 when the page is unchanged),
while s-maxage lets the edge hold the page for the TTL.
Details:
- The decision is the page cache's own "would I store this" test, so the edge never holds anything the page cache would refuse.
- An existing
privateorno-storeheader on a storable response is respected: the public header is not set. - On a response that is not storable, the private header is set only when no
Cache-Controlheader exists yet. A header another plugin already sent is left as it is. - The headers are set as part of the page cache's store step, so they need
page_cacheon. With the page cache off, no response carries them; the Cloudflare tab warns about this. - A request with
?bb_raw=1skips the output buffer and therefore gets neither header. - With
cf_edge_ttlat0, a page-cache hit carriesCache-Control: no-cache, must-revalidate, as before 0.3.0.
The Cache Rule the zone needs#
Cloudflare does not cache HTML by default. A Cache Rule has to make it eligible; the headers above then decide per
response. The Cloudflare tab prints the rule for your site, built from your reject_cookies list by
cache_rule_text(). With the default cookie list it reads:
Cloudflare dashboard > Caching > Cache Rules > Create rule
Rule name: BB Cache public HTML
When incoming requests match, Custom filter expression (Edit expression):
(http.request.method eq "GET" and not starts_with(http.request.uri.path, "/wp-admin") and not starts_with(http.request.uri.path, "/wp-json") and not http.request.uri.path contains ".php" and not http.cookie contains "wordpress_logged_in_" and not http.cookie contains "wp-postpass_" and not http.cookie contains "comment_author" and not http.cookie contains "woocommerce_cart_hash" and not http.cookie contains "woocommerce_items_in_cart" and not http.cookie contains "wp_woocommerce_session_")
Then: Cache eligibility = Eligible for cache
Edge TTL = Use cache-control header if present, bypass cache if not
Browser TTL = Respect existing headers
The cookie conditions matter: without them a logged-in visitor could be served the edge's anonymous copy.
Copy the rule from the Cloudflare tab rather than from this page, so that it carries your own cookie list. Only
cookie names made of letters, digits, _ and - are included. If you change reject_cookies later, update the rule
in Cloudflare too; the plugin does not manage Cache Rules.
Caveats#
Query-string variants. The page cache ignores marketing parameters and serves /?utm_source=x from the same
file as /, with the public header. Cloudflare stores each such URL as a separate entry, and a URL purge of / does
not clear them. They expire with the edge TTL. Choose a short TTL, or all mode if stale campaign links are not
acceptable.
Separate mobile cache. cache_mobile keeps a phone variant per URL, but the edge keeps one copy per URL for
every device (device-type cache keys are a Cloudflare Enterprise feature). With an edge TTL set, turn cache_mobile
off unless the theme prints different HTML for phones. The Cloudflare tab shows this warning while both are on.
WebP variant. cache_webp keeps a variant for browsers that accept image/webp, but the edge ignores the
Accept header for HTML, so one variant would reach every browser. With an edge TTL set, turn cache_webp off and
let Cloudflare convert images at the edge instead (Polish). The Cloudflare tab shows this warning while both are on.
Recursive purges. See the note on urls mode above: purging a post removes everything beneath its path locally
but only the permalink at the edge.
Timing. Purges are sent at the end of the request that caused them and block it for the duration of the API calls.
Tools#
On the Cloudflare tab, below the form:
| Tool | What it does |
|---|---|
| Status line | Shows where the zone ID and token come from (wp-config.php, settings, or missing or malformed), whether the purge mirror is on, and the edge TTL. The token itself is never shown. |
| Verify token | Calls user/tokens/verify and checks that the token status is active, then tries to read the zone. A zone that cannot be read is reported but does not fail the check, since purging does not need zone read. |
| Check the edge | Requests the home page twice through its public address and shows, per request: HTTP status, cf-cache-status, age, cache-control and x-bb-cache. The result is kept for 10 minutes. |
| Purge everything at Cloudflare | Sends purge_everything for the zone now, regardless of the cf_purge setting. |
Reading Check the edge:
| Second request shows | Meaning |
|---|---|
HIT |
The edge serves this URL from cache |
DYNAMIC or empty |
Cloudflare does not cache this HTML: the Cache Rule is missing or does not match |
BYPASS |
The response was private, or the Cache Rule excluded the request |
anything else (MISS, EXPIRED, …) |
Reported as is |
no cf-ray header on the first request |
The request did not pass through Cloudflare: DNS is not proxied, or the server resolves its own host name locally |
The same tools exist in WP-CLI, where check accepts any URL:
wp bb-cache cloudflare verify
wp bb-cache cloudflare check https://example.com/pricing/
wp bb-cache cloudflare purge https://example.com/ https://example.com/pricing/
wp bb-cache cloudflare purge
wp bb-cache cloudflare log
Purge log#
The last 20 purge calls are kept in the bb_cache_cf_log option and listed on the Cloudflare tab and by
wp bb-cache cloudflare log. Each entry has the time (UTC), what was purged (everything or the number of URLs),
the result, Cloudflare's error message on failure, and up to three sample URLs.
A failed purge is also written to the PHP error log:
BB Cache: Cloudflare purge (urls) failed: <Cloudflare error code and message>
The token is never written to either log.
Setup checklist#
- Create the API token with Zone > Cache Purge > Purge for the zone.
- Define
BB_CACHE_CF_TOKENandBB_CACHE_CF_ZONEinwp-config.php(or enter both on the Cloudflare tab). - Run Verify token.
- Turn on Purge Cloudflare when the page cache purges and choose the purge mode.
- For edge caching of HTML: turn off
cache_mobileandcache_webp, setcf_edge_ttl(for example900), save, and create the Cache Rule shown on the tab. - Run Check the edge. The second request should show
HIT. - Edit a page and confirm a new entry in the purge log.