Cache HTML on Cloudflare Free
Manages one Cloudflare Cache Rule for this site’s hostname, purges exact URLs, and purges this site’s hostnames when everything has to go. Everything works on the Free plan: no Workers, no APO, no Argo, no paid plan.
Off by default. Configure it on the Cloudflare tab, or in step 4 of Setup.
Credentials
Two authentication modes, chosen with the Authentication setting:
| Mode | What you enter | Notes |
|---|---|---|
| Scoped API token (recommended) | Scoped API token | Needs exactly three permissions on this site’s zone (below). |
| Global API Key (legacy) | Global API Key and Cloudflare account email | Account-wide. Use it only to create a scoped token, then clear it. |
The token needs these permissions, in the wording Cloudflare’s dashboard uses:
Zone → Zone → ReadZone → Cache Purge → PurgeZone → Cache Rules → Edit(permission group “Cache Settings Write”)
Saved secrets are encrypted at rest in the options table, and a blank field on save keeps the secret already stored. The same values can come from wp-config.php instead, which takes precedence over saved settings:
// Scoped token mode.
define( 'GTPERF_CLOUDFLARE_API_TOKEN', '…' );
// Or Global API Key mode (both constants required).
define( 'GTPERF_CLOUDFLARE_GLOBAL_API_KEY', '…' );
define( 'GTPERF_CLOUDFLARE_EMAIL', 'you@example.com' );
// Optional: the domain used for zone discovery.
define( 'GTPERF_CLOUDFLARE_DOMAIN', 'example.com' );
A defined token constant forces token mode. The two Global API Key constants force Global API Key mode when no token constant exists. See Constants Reference.
Getting a token
The Get an API token panel offers two routes:
- Create token at Cloudflare opens Cloudflare’s Create Token form in your browser with the token name (
GT Performance - <domain>) and zone filled in. Cloudflare does not preselect the permission groups, so pick the three above, then paste the token into Scoped API token and save. - Create scoped token appears when a Global API Key and account email are on file. GT Performance mints a token limited to this site’s zone with exactly those three permissions, tests it (token verification plus a cache-ruleset read) before saving it, and switches the connection to token mode. Clear the Global API Key afterwards.
Cloudflare offers no sign-in flow a plugin could use, so there is no “Connect with Cloudflare” button.
Connect and synchronize
- Save your credentials. Optionally set Domain (used for zone discovery) or Zone ID (skips discovery).
- Click Connect/sync Cloudflare (on the Cloudflare tab, the Dashboard, or the Setup tab).
Sync discovers the zone when Zone ID is blank (it looks up the domain’s last two labels first, then longer names, and matches only active zones), saves the Zone ID, creates or updates this site’s managed rule, and turns the integration on. When the zone has no cache ruleset yet, sync creates one named “GT Performance cache rules”.
After writing the rule, every sync purges this site’s hostnames at Cloudflare. Pages Cloudflare stored while the rule was absent or different (a page WordPress sent without the plugin’s headers, for example, which Cloudflare keeps for its default two hours) are not known to be right under the new rule. A failed purge is reported: the admin notice and wp gt-performance cloudflare sync say the rule is synchronized but the purge failed, and pages stored earlier may be served until they expire.
Every other rule in the zone is left alone. Sync does not save a copy of the zone’s ruleset; releases before 1.2.0 wrote one to the gt_performance_cloudflare_backup option but never read it, and uninstall still removes that option.
The managed rule
GT Performance compiles your bypass policy (paths, cookie prefixes, and query parameters from the Exceptions tab, plus everything the active commerce adapters and integrations add) into one rule per hostname. With default settings on example.com, the expression begins:
(http.host eq "example.com")
and (http.request.method in {"GET" "HEAD" "PURGE"})
and (not (http.request.uri.path eq "/wp-admin" or starts_with(http.request.uri.path, "/wp-admin/")))
…
and (not http.cookie contains "wordpress_logged_in_")
…
and (not (starts_with(http.request.uri.query, "s=") or http.request.uri.query contains "&s="))
…
What each part does:
- Host. The rule applies only to this site’s hostname.
- Methods.
GETandHEAD, plusPURGE, because Cloudflare evaluates cache rules for its internal single-file purge requests. Without it, exact-URL purges do not clear the edge copy. - Paths match on segment boundaries, so
/checkout/protects/checkoutand everything below it without matching/checkout-summary. - Cookies exclude any request whose cookie header contains a bypass prefix.
- Query parameters are anchored to a parameter boundary, so bypassing
sdoes not also exclude?utms=. The expression avoidsconcat(), which Cloudflare allows only once per expression.
The rule’s action:
| Setting | Value |
|---|---|
| Cache eligibility | Eligible for cache |
| Edge TTL | Respect origin (default), or override origin when Cloudflare edge cache lifetime is positive |
| Browser TTL | Respect origin |
| Serve stale while updating | On |
| Cache deception armor | On |
| Ignore query string order | On |
| Cache by device type | Follows Separate cache for mobile HTML on the Page Cache tab |
| Custom cache key | Excludes your ignored tracking parameters, only where the plan accepts it |
A custom cache key is an Enterprise capability. Sync always tries it first. When the plan rejects it, sync retries without it and remembers that, so drift checks compare against the rule your plan can actually store. The plan preview then reads “Not supported on this plan, so query-string exclusions are skipped”.
The expression is never truncated. If Cloudflare rejects it, the sync fails and the notice shows Cloudflare’s reason.
One rule per hostname
Each site’s rule has its own ref, gt-performance-html- followed by the first 16 hex characters of the SHA-256 of the lowercased hostname. Sites that share a zone (example.com and shop.example.com on separate installs) therefore each keep their own rule: one site’s sync never overwrites another’s, and one site’s deactivation never deletes another’s.
Releases before 1.2.0 used one fixed ref, gt-performance-free-html-cache, for every site. A rule under that ref still belongs to the site whose hostname its expression names (it starts with (http.host eq "<host>")), and that site keeps managing it under the old ref: Cloudflare does not let a ref change, so an update keeps the ref it has, and the preview does not count the old ref as drift. A legacy rule that names another host is left to that site.
Edge cache lifetime
Cloudflare edge cache lifetime (cloudflare.edge_ttl) defaults to 0, which respects the origin. The origin sends Cache-Control: public, max-age=<browser TTL>, s-maxage=<fresh time left>, stale-while-revalidate=<stale TTL> on cacheable pages and no-store (plus CDN-Cache-Control and Cloudflare-CDN-Cache-Control: no-store) on everything it bypasses, so Cloudflare stores only what the origin would.
A positive value (up to 31536000 seconds) switches the rule to override origin freshness. Once the edge ignores the origin’s no-store, any unrecognized query parameter could mint a private edge entry, so the rule is narrowed to requests with no query string at all (http.request.uri.query eq "") and the query-parameter terms drop out. Keep 0 unless you have a specific reason.
Explain this page reports whether the managed rule matches a URL and flags the one unsafe combination: an overriding lifetime on a request the origin refuses to cache. See Diagnostics.
Rule budget and overlaps
The plugin budgets for Cloudflare Free’s 10 cache rules. Sync updates the existing managed rule in place, and refuses to create a new one when the zone already holds 10 rules.
It also reads every other enabled cache rule in the same phase that names this exact hostname (in quotes, so a rule for shop.example.com is not mistaken for one for example.com) or never tests http.host at all (so it applies to every hostname in the zone). These are listed as potential overlaps, marked when they match every hostname or bypass cache. GT Performance reports them and never edits them. Another site’s managed rule in the same zone counts toward the 10-rule budget like any other rule.
Preview before you sync
Preview live plan (in the Cloudflare Free rule compiler panel) reads the zone without changing it and shows:
- Planned operation: create, update, or none
- Free rule budget: rules used out of 10
- Managed-rule drift: in sync, or needs reconciliation
- Potential overlaps, with each rule’s description and expression
- Custom cache key: applied, or not supported on this plan
- Compiled expression: the exact text sync will send
wp gt-performance cloudflare plan prints the same plan as JSON.
Keeping the rule current
Saving settings changes the origin policy immediately but never rewrites the Cloudflare rule on its own. Run Connect/sync Cloudflare again after you change bypass paths, cookies, or query parameters, after a commerce plugin is activated or removed, and after upgrading if the preview shows drift. While Cloudflare is enabled, a settings import, restore, or applied AI proposal that touches cache.* or cloudflare.* shows a reminder to sync.
Purging
Automatic purges
When the origin purges URLs (post saves, comments, term changes, commerce stock and price updates), the same URLs are queued for Cloudflare, de-duplicated, and sent once at the end of the request. Query variants of a purged page (see Page Cache) are sent with it.
A full origin purge sends one purge-by-hostname request for this site’s canonical hosts (the hosts of home_url() and site_url(), plus any added with gt_performance_canonical_hosts) instead of Cloudflare’s purge-everything, so other sites and subdomains in the same zone keep their cache. Purge by hostname is available on every plan. Full origin purges happen when you use Purge GT cache, when you publish with Cache clearing after publishing set to Entire page and edge cache, when you switch themes, save the Customizer or a menu, or update WordPress, a plugin or a theme, and when a settings save in wp-admin changes a setting that reaches cached pages (see Page Cache). The same hostname purge is sent, at the edge only, by cloudflare purge without --page-url, by every sync, and when you disconnect or deactivate (below). See Purging.
The Cloudflare integration loads whenever a purge fires, so purges from REST API edits authenticated with an Application Password, and from signed-out visitors’ requests (an auto-approved comment, a checkout that sells a product out), reach the edge as well as the origin.
How purge requests are sent
- Batches of 100. Exact URLs are sent to Cloudflare’s purge endpoint 100 entries per request.
- Device variants. With Separate cache for mobile HTML on, each URL is purged as the plain URL plus three entries carrying
CF-Device-Type: desktop,mobile, andtablet, matching the rule’s cache-by-device-type key. - Retries. A transport failure, HTTP 429, or HTTP 5xx is retried up to three times through WP-Cron. Each retry waits the longer of 60 seconds doubled per attempt and Cloudflare’s
Retry-After(capped at one day). Only the batches Cloudflare did not process are retried, with their device headers intact, and a retry never runs against a zone other than the one it was queued for. Other errors (a missing permission, for example) are not retried. - Full purge wins. A successful hostname purge cancels every queued URL purge and scheduled retry.
The Latest Cloudflare purge panel on the Cloudflare tab shows the most recent result: Accepted by Cloudflare, Failed; retry scheduled, or Failed; action needed, with the time and Cloudflare’s message. “Accepted” means Cloudflare took the request. It does not prove what visitors now receive.
Interactive purges wait
Purge GT cache, the admin-bar purge actions, wp gt-performance cache purge, and AI operations send their Cloudflare purge immediately and wait for the answer. If Cloudflare fails, the notice says the local cache was cleared but Cloudflare did not finish.
Verify a purge
Purge and verify this URL (admin bar) and wp gt-performance cache verify --page-url=<url> purge one URL, wait for Cloudflare, fetch the public page twice, and record a receipt. A receipt is verified only when the origin entry was removed, both fetches returned the same 200 response that was not private, and (with Cloudflare enabled) the first fetch was not an old Cloudflare HIT or STALE with a positive Age. Receipts appear under Tools → Verified purge receipts. See Purging.
WP-CLI
wp gt-performance cloudflare status
wp gt-performance cloudflare plan
wp gt-performance cloudflare sync
wp gt-performance cloudflare purge --page-url=https://example.com/some-post/
wp gt-performance cloudflare purge # purges this site's hostnames
wp gt-performance cloudflare disconnect [--forget]
cloudflare purge talks to Cloudflare directly and touches only the edge. Its --page-url must be on one of this site’s canonical hosts. wp gt-performance cache purge clears the origin first and then the edge. See WP-CLI.
Disconnecting and deactivating
Disconnect Cloudflare (on the Cloudflare tab while the integration is on) and wp gt-performance cloudflare disconnect:
- delete this site’s managed Cache Rule and no other rule;
- purge this site’s hostnames at Cloudflare, so no page stored under the rule lingers;
- turn the integration off.
The credentials and Zone ID stay, so reconnecting is one sync away. --forget also deletes the saved token, Global API Key, account email and Zone ID. If the purge fails, the notice (or a WP-CLI warning) says so, and pages Cloudflare already stored stay until they expire unless you purge them in the Cloudflare dashboard.
Deactivating the plugin does the same first two steps, best effort and without blocking deactivation, but leaves the integration on for when you reactivate. It is skipped while xCloud owns the edge. After reactivation the Cloudflare tab warns that GT Performance deleted its rule when it was deactivated and that Cloudflare is not caching pages for this site until you run Connect/sync Cloudflare. If the zone could not be reached during deactivation, the rule stays and cloudflare disconnect can finish the job.
Connection check
Run connection check walks each stage in order and stops at the first failure. Two stages warn rather than fail, and the summary says when a stage needs attention:
| Stage | What it checks |
|---|---|
| Integration enabled | Warns when the integration is switched off |
| Edge ownership | Fails when xCloud owns the edge (see below) |
| Credentials | The token, or the Global API Key and email, are readable |
| Authentication | Cloudflare accepts them; a token must report active |
| Zone lookup | The saved Zone ID can be read, or the domain resolves to an active zone; reports the zone and plan name |
| Traffic through Cloudflare | Warns when a DNS record for the site’s host is DNS only (grey cloud), because visitors then go straight to the origin and the rule and purges have no effect. When the token cannot read DNS (the recommended token has no DNS permission), it checks the home page for Cloudflare’s CF-Ray header instead |
| Automatic Platform Optimization | Warns when APO is on for the zone: APO caches HTML with its own rules and purges through Cloudflare’s plugin, so GT Performance’s purges do not clear APO’s copies. Turn APO off, or turn off this integration and let APO own the edge. Skipped when the token cannot read the setting |
| Cache rules read | The zone’s cache ruleset can be read (a zone with no ruleset yet passes) |
| Cache rules write | Rewrites the managed rule with its own current contents, which changes nothing but proves the write permission. Skipped until the rule exists. |
This matters because reading rules and writing them are different permissions, and Cloudflare returns a similar rejection for several missing ones. Failures show Cloudflare’s own error text and code, the endpoint that failed (with zone and ruleset IDs masked), and a permission hint on HTTP 400, 401, and 403. A transport failure is reported as a network or firewall problem, not a credential problem. The check runs from the admin only.
Edge ownership
Two systems editing cache behavior on one zone produces results nobody can explain. When the xCloud integration is on and xCloud reports either its Cloudflare Enterprise add-on or its free edge cache, xCloud owns the edge:
- GT Performance stops sending its own automatic Cloudflare purges.
- Connect/sync Cloudflare and
wp gt-performance cloudflare syncrefuse to run. - The connection check fails at Edge ownership.
- The health report,
wp gt-performance doctor, and the xCloud status panel warn while direct Cloudflare is also enabled.
Disable one of the two.
When xCloud’s automatic cache purge plugin is installed (1.3.6 and later), the choice is made for you. The site is served from xCloud’s Cloudflare zone, so direct Cloudflare is paused: its settings are kept, it makes no API calls, and the connection check reports Edge ownership as a warning instead of a failure. GT Performance purges xCloud Cloudflare Enterprise through that plugin. See xCloud.
Host-managed layers
On a site behind a Cloudflare layer your token cannot address (a host-managed edge, or a proxy in another account), a purge can be accepted for your zone while visitors keep receiving a copy held by the other layer.
To see a true origin render, request the page with a cache-busting query string such as ?nocache=1 rather than re-fetching the bare URL. The origin treats an unrecognized parameter as a bypass: it renders the page fresh and answers Cache-Control: no-store, private, max-age=0. With Diagnostic logging on, the response also carries X-GT-Cache: BYPASS and an X-GT-Cache-Reason naming the parameter. If that render is current and the bare URL is not, purge the other layer from its own dashboard. See Troubleshooting.