Skip to content

Cache Warming

Gaurav Tiwari edited this page Sep 28, 2026 · 2 revisions

Cache Warming

After a full purge every page is cold, and the first visitor to each one pays for the render. A warm run rebuilds pages in the background from your sitemaps, most recently changed first, and records for every URL whether the origin actually stored a page.

Warming exists to fill GT Performance's own page store, so it does not run in optimize-only mode, where the host's cache stores pages instead. No warm run is queued after a full purge, the stale-page refresh is skipped, and Start warm run and wp gt-performance cache warm refuse with a message that nothing was queued.

Starting a run

A run starts:

  • Automatically after a full purge, when the page cache and Warm cache after a full purge are both on. The run is queued 30 seconds later, and a burst of full purges (a theme switch plus a menu save) within a minute queues one run, not several.
  • From Tools > Cache warming > Start warm run.
  • From WP-CLI:
wp gt-performance cache warm
wp gt-performance queue run          # process it now instead of waiting for cron
wp gt-performance cache warm-status

To rebuild a single page, use Warm this URL in the admin bar. See Purging.

Starting a run while another is unfinished marks the earlier one superseded. Its already-queued preloads still run, but their results no longer count against it.

Where URLs come from

Page Cache > Cache warming > Sitemap sources (cache.preload_sitemaps) takes up to 10 sitemap URLs from this site, one per line. When it is empty, the run uses:

  1. the WordPress core sitemap, /wp-sitemap.xml
  2. every Sitemap: line in /robots.txt, which is where SEO plugins announce their own sitemaps

Only sources on the same scheme, host and port as the home URL are used, and at most 10 in total. The home page is always warmed first.

Discovery rules:

  • Each discovery job fetches at most two sitemaps, so a large site is read across many short background jobs instead of one long request.
  • Sitemap indexes are followed up to five levels deep.
  • Redirects are not followed automatically. Each hop is recorded as its own target, must stay on this site, and counts as a level.
  • A sitemap response is read up to 2 MB, robots.txt up to 64 KB. A truncated sitemap is reported.
  • Entries on other domains are skipped and reported.
  • URLs the cache would bypass anyway (a bypass path, an unknown query parameter, and so on) are never queued.
  • A run holds at most 50,000 targets, sitemaps and variants included.

Progress lives in the gtperf_warm_targets table, so a worker that dies mid-run resumes where it stopped. Every target is identified by its normalized URL, so a sitemap that lists itself, or a cycle of indexes, is fetched once.

Recent pages first

Each sitemap entry is ranked by its <lastmod>:

Priority Targets
First The home page
Next Entries modified in the last 7 days
Then Everything else, including entries without a <lastmod>

With Separate cache for mobile HTML on, every URL gets a second target for the mobile variant, requested with a phone user agent.

Batches

Warming dispatches URLs per warming batch (cache.preload_max_urls, default 200, maximum 2,000) preload jobs at a time, then waits for all of them to report before queuing the next batch. It checks once a minute. After 30 checks without a result, the missing ones are marked failed with no_result and the run carries a lost_results warning instead of waiting forever. Cancelled or lost preload jobs are the usual cause.

Set the batch size to 0, or turn Warm cache after a full purge off, to stop rebuilding without turning caching off. A run that reaches dispatch in that state skips its remaining URLs as preload_disabled and ends partial.

Warm jobs run at the same priority as ordinary preloads, ahead of unused-CSS generation. Pausing optional queue work pauses warming too. See Tools.

Cache size budget

A run never warms more pages than the cache is allowed to keep. Each batch is capped by the room left under cache.entry_budget (default 5,000 entries, 0 for no limit), counting every URL target this run has already queued or reported. When the budget is used up, the remaining URLs are skipped as capacity_limited and the run ends in the capacity_limited state. Because the home page and recently changed pages go first, those are the ones a budget smaller than the site keeps.

The same budget drives eviction in the hourly garbage collector. See Page Cache for how to change it.

Per-page results

A 200 response is not proof of a warm cache. Each preload is classified from the evidence:

Result Detail Meaning
origin_ready rebuilt The origin stored a fresh page during this request
origin_ready existing A fresh origin entry was already there
edge_observed cf_hit, cf_stale, cf_updating, cf_revalidated Cloudflare answered from its cache and no fresh origin entry exists
requested no_artifact, or gt_<status> HTTP 200, but nothing was stored. The detail carries the X-GT-Cache value when there was one
skipped a bypass reason, or invalid_url The URL is not cacheable, so it was never requested
skipped redirect_<code> (for example redirect_301), http_<code> for a 4xx (for example http_404) The page answered with a redirect or a client error. That is the page's answer, not a hiccup, so it is not retried
failed http_<code> for a 5xx or any other non-200 status, request_error, no_result A server error, a transport error, or no report

Preload requests carry a signed X-GT-Preload token (an HMAC derived from AUTH_KEY, valid for ten minutes), do not follow redirects, time out after 15 seconds, and identify themselves as GT-Performance-Preloader/<version>. The token is what lets a preload rebuild a stale page; the drop-in ignores the header without a valid one. A failed preload job (a server error or a transport failure) is retried with backoff, up to three attempts in total. A redirect or a 4xx is recorded as skipped at once: a sitemap that still lists a moved post gets the same 301 on every retry.

Run states and warnings

State Meaning
discovering Reading sitemaps
warming Dispatching preload batches
complete Every sitemap read and every URL reported
partial Finished with a sitemap failure or one of the warnings below
capacity_limited Stopped at the cache size budget
superseded Replaced by a newer run

Warnings recorded on a run: sitemap_failed, sitemap_truncated, sitemap_skipped (a redirect left the site or went too deep), depth_limit, target_cap, foreign_entries, lost_results, preload_disabled. Any of sitemap_truncated, depth_limit, target_cap, sitemap_skipped or lost_results, or a failed sitemap, makes the run partial.

Where to see it

Tools > Cache warming shows the latest run: state, origin ready, edge only, waiting, failed, plus how many URLs answered without a stored page, how many were skipped, how many sitemaps were read or failed, when it last changed, and its warnings.

wp gt-performance cache warm-status prints the state, the sources (as paths), the warnings and every target count by kind and status.

The health report (Tools, Site Health, wp gt-performance health) includes the latest run and warns when it is partial, capacity_limited, has failed URLs, or has made no progress for 30 minutes while still running. See Diagnostics.

Target rows are kept for 7 days and the last 10 run summaries are kept in the gt_performance_warm_runs option.

Settings

Label Key Default
Warm cache after a full purge cache.preload On
URLs per warming batch cache.preload_max_urls 200 (0 to 2,000)
Sitemap sources cache.preload_sitemaps Empty (WordPress sitemap plus robots.txt)
Cache size budget (no admin field) cache.entry_budget 5,000

Warm cache after a full purge also gates the stale-revalidation sweep described on Page Cache. Preloads queued after a publish or update run regardless of it.

AI assistants with operate permission can queue preloads for up to 20 URLs through the gt-performance/preload-urls ability. See AI and MCP.

Related

Page Cache · Purging · Tools · WP-CLI · Settings Reference

Clone this wiki locally