Skip to content

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-Control contract 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_settings option 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_everything call is sent and the queued URLs are dropped.
  • Otherwise the queued URLs are sent to the zone's purge_cache endpoint as files, 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:// or https:// 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 private or no-store header 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-Control header 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_cache on. With the page cache off, no response carries them; the Cloudflare tab warns about this.
  • A request with ?bb_raw=1 skips the output buffer and therefore gets neither header.
  • With cf_edge_ttl at 0, a page-cache hit carries Cache-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#

  1. Create the API token with Zone > Cache Purge > Purge for the zone.
  2. Define BB_CACHE_CF_TOKEN and BB_CACHE_CF_ZONE in wp-config.php (or enter both on the Cloudflare tab).
  3. Run Verify token.
  4. Turn on Purge Cloudflare when the page cache purges and choose the purge mode.
  5. For edge caching of HTML: turn off cache_mobile and cache_webp, set cf_edge_ttl (for example 900), save, and create the Cache Rule shown on the tab.
  6. Run Check the edge. The second request should show HIT.
  7. Edit a page and confirm a new entry in the purge log.