Preloading, or warming, is one of those settings people turn on because it sounds like it must help. It sits in the caching plugin, it promises faster pages, and there is no obvious reason not to enable it.

There is a reason, though. Preloading does not make your site faster. It moves work from the visitor to your server, and whether that is a good trade depends on how much traffic you get, how many pages you have, and how often your cache is emptied. On some sites it is the most useful caching setting available. On others it burns resources every hour rebuilding pages nobody was going to request.

By the end of this you will know what preloading does, whether your site benefits, how to do it by hand with your sitemap and a terminal, and how to do it without knocking your own server over.

The decision is the important part, and the first three sections cover it without a line of code. If you decide preloading is right for your site, most caching plugins have a preload or warm-up option you can simply switch on, and the hand-rolled version further down is for when you want control over which pages get warmed and how hard your server gets pushed.

Before you start

For the manual route you need the URL of your XML sitemap, which is the machine-readable list of your pages that search engines use, and a terminal with curl or wget. Those are small programs for fetching a web address without a browser. Both ship with macOS and Linux, and curl is built into current Windows. To run the crawl on a schedule you need somewhere to run it from: your own machine, a cron job on the server (a cron job just means a command the server runs at set times, and most hosting panels have a screen for setting them up), or a small rented server.

If none of that appeals, skip Steps 4 and 5. Your caching plugin’s own preload setting does the same job, and the reasoning in Steps 1 to 3 is what decides whether to turn it on.

Nothing here writes to your database or your files, so no backup is needed. The risk worth naming is load. A preload crawl is real traffic hitting your own site, and an aggressive one on cheap shared hosting can slow the site down, or get your connection temporarily blocked for looking like an attack. Start slow.

If you are not sure whether your site caches pages at all, check that first. Preloading a site with no page cache does nothing except make work for your server.

Step 1: What preloading actually is

Page caching is passive. Every time somebody loads a page, WordPress has to assemble it: run the code, ask the database for the content, build the finished HTML, which is the file of text and tags the browser turns into a page. A page cache keeps that finished HTML the first time it is built and hands the stored copy to everyone afterwards. But it only starts working once a visitor has triggered it, so the first person to ask for any given page pays the full cost of building it.

Preloading is the same thing done deliberately. Instead of waiting for a visitor to trigger the build, you request the page yourself so the cache is already populated. The server neither knows nor cares that the request came from a script.

So preloading is not a different kind of caching, and it does not make a cached page faster. A warm page performs identically whether it got warm because you crawled it or because a customer landed on it. The only thing that changes is who absorbed the slow first load, which I covered in why the first visitor after a cache clear waits.

That makes the trade obvious. You are spending server capacity, on your schedule, to save a visitor spending their patience on theirs. Whether that is worth it is arithmetic, not best practice.

Step 2: The honest case against preloading

Plenty of sites should leave this alone.

Your cache never goes cold anyway. If a page gets a visit every few minutes and your cache lifetime is a day, ordinary traffic keeps it warm. Preloading then adds requests for pages that would have been served from cache regardless. A busy blog or a shop with steady orders falls here, and preloading gains nothing measurable while adding load.

It costs real resources. Every preloaded URL is a page built from scratch, with all the code, database queries and memory that involves. Shared hosting plans usually cap how much processing you get and how many requests can be in flight at once, so this is genuinely expensive, and a large crawl after every post save can push you over the cap. When that happens the host throttles you, which makes the site slower for everyone.

It can hammer a small server. The worst moment to fire off a burst of simultaneous requests is right after the cache has been emptied, because there is no cache left to absorb any of them. Preloaders that crawl everything several pages at a time have taken sites down.

It becomes a treadmill on big sites with short lifetimes. The cache lifetime is how long a stored copy is kept before it is thrown away. With five thousand URLs and a one hour lifetime, a full preload cannot finish and stay ahead, so you rebuild pages that expire before anyone visits them. The fix there is a longer lifetime and a shorter list of pages to warm, not a faster crawler.

It hides a real problem. If your uncached render takes several seconds, preloading conceals that from most visitors while leaving it in place for everything that cannot be cached: search results, filtered archives, logged in views, the checkout. Warming is no substitute for fixing why a page takes seconds to build, which is the ground I cover in speeding up WordPress and fixing Core Web Vitals.

Step 3: The cases where it genuinely helps

On the right site the benefit is real and immediate.

Low traffic sites. The clearest case. A brochure site, a local service business, a portfolio: if a page gets a handful of visits a week, almost every visitor is the first visitor, because the cache expires between visits. Preloading turns “nearly everyone waits” into “nobody waits”, and costs little because there are few pages.

Large sites where most pages are cold. A site with thousands of URLs has a long tail getting occasional search traffic. Those pages are always cold when someone finds them, and they are the pages where a visitor has no prior relationship with you and no reason to wait.

Sites that purge aggressively. Purging means throwing the stored copies away, and some setups purge the lot on any post save, comment, stock change or plugin update. If your cache is emptied several times a day, your visitors meet cold pages several times a day. Either fix the over eager purging, which is better, or warm the cache back up each time.

After a deploy or bulk edit, or before a spike. This applies to every site regardless of size. You have pushed a theme change, run a find and replace across five hundred posts, or you have an email going out at nine. Everything is deliberately cold, and warming the important pages takes minutes and removes the risk that the first arrivals all hit an empty cache.

If none of those describe your site, skip preloading and spend the effort on your cache lifetime.

Step 4: Build a URL list from your sitemap

This step and the next are the terminal ones. If you are letting a plugin do the crawling, read them for the reasoning and skip the commands.

Your sitemap already lists the URLs you want indexed, which is a close match for the URLs worth warming. WordPress core serves one at /wp-sitemap.xml; Yoast and Rank Math use /sitemap_index.xml. Open yours in a browser to see which you have. On most sites it is an index, meaning a sitemap of sitemaps, so the script pulls the index first and then each of the smaller ones it points at:

#!/usr/bin/env bash
# No pipefail here on purpose: grep exits 1 when it matches nothing, which
# happens legitimately on a flat sitemap, and pipefail would kill the script
# with no output and no explanation.
set -eu

SITEMAP="https://example.com/wp-sitemap.xml"
WORKDIR="$(mktemp -d)"

# --compressed matters: several sitemap plugins serve gzipped XML.
# Every <loc> in the index gives you a child sitemap.
curl -s --compressed "$SITEMAP" \
  | grep -oE '<loc>[^<]+</loc>' \
  | sed -E 's~</?loc>~~g' \
  > "$WORKDIR/sitemaps.txt" || true

# Every <loc> in each child gives you a page URL.
: > "$WORKDIR/urls.txt"
while read -r child; do
  curl -s --compressed "$child" \
    | grep -oE '<loc>[^<]+</loc>' \
    | sed -E 's~</?loc>~~g' \
    >> "$WORKDIR/urls.txt"
done < "$WORKDIR/sitemaps.txt"

# De-duplicate, since the index can appear in its own child list.
sort -u "$WORKDIR/urls.txt" -o "$WORKDIR/urls.txt"
wc -l < "$WORKDIR/urls.txt"

Look at that count before going further. If it says forty, warm the lot. If it says four thousand, you have a decision about scope rather than a crawl to run.

Trim the list first. Drop anything that is never cached or pointless to warm: cart, checkout, account and search results, all of which are different for every visitor, plus any URL with a ? in it, since those are one-off addresses that caches usually skip. Attachment pages and page twelve of an archive rarely justify the effort either.

# Drop the pages excluded from caching anyway, plus query strings.
grep -vE '/(cart|checkout|my-account)/|\?' "$WORKDIR/urls.txt" > "$WORKDIR/warm.txt"

If your sitemap is enormous, take the recent posts sitemap rather than the whole index. A partial warm of the pages people land on beats a complete warm you cannot afford to run.

Step 5: Crawl it, politely

The crawl is the easy part. Doing it gently is what matters.

# One request at a time, with a pause between each. --compressed asks
# for the same gzip or brotli variant a browser would, so you warm the
# copy a real visitor gets rather than a separate uncompressed one.
while read -r url; do
  code=$(curl -s -o /dev/null --compressed -w '%{http_code}' "$url")
  printf '%s  %s\n' "$code" "$url"
  sleep 2
done < "$WORKDIR/warm.txt"

The wget equivalent, if you prefer it:

# --delete-after fetches the page properly then discards it.
# Do NOT use --spider: that sends a HEAD request, which some caching
# layers will not store, so you would crawl everything and warm nothing.
wget --quiet --delete-after --wait=2 --limit-rate=500k \
     --input-file="$WORKDIR/warm.txt"

Why politeness is not just good manners:

  • The cache is empty when you start. Every request in the first pass builds a page from scratch, and asking for several at once multiplies the cost at the exact moment your server can least take it.
  • Hosts have protection layers. A rapid burst of requests from one address looks like a scraper, so Cloudflare, a firewall or the host’s own limiter may start refusing you or showing a “checking your browser” page. In some setups those get stored in the cache, which is worse than doing nothing.
  • Real visitors are still arriving. They are queueing for the same PHP workers as your crawl. A worker is one slot for building one page at a time, and a small plan might have two, so a crawler occupying one leaves the site running at half capacity.

Start with a two second delay and one request at a time. If the site stays responsive, shorten the delay. If it feels sluggish, lengthen it or run the crawl overnight. Watch the three-digit status codes the script prints: 200 is fine, 404 means the sitemap lists a page that no longer exists, and 429 or 503 mean the server is telling you to slow down.

Step 6: Decide what triggers it

A manual crawl covers deploys and bulk edits, the case that matters most. Anything more regular needs a trigger, and the straightforward option is a cron job at a quiet hour, which is fine for a brochure site where the goal is keeping twenty pages warm.

The problem is that a schedule does not know when your cache was emptied. A crawl at three in the morning does nothing for a purge at eleven, and those hours in between are exactly when your visitors are around. Scheduled warming is always either too frequent, wasting work, or too infrequent, missing the purge that mattered. The trigger you actually want is the purge itself.

Warming on purge, without running the crawl yourself

That is the gap ABCode Cache Warmer fills. It does the same job as the crawl above, but it starts when the cache is cleared rather than at a fixed time, so pages that just went cold are rebuilt straight away.

It is free on wordpress.org, and it:

  • Sits alongside your existing caching plugin rather than replacing it, working with WP Rocket, LiteSpeed, W3 Total Cache and others.
  • Lets you choose the scope, the same decision as step 4: posts, archives, the sitemap, or your own list of URLs.
  • Reads the cache headers back and reports what happened per URL, so you can see whether a page was really stored.
  • Measures your response time and server load to decide how hard to push, which is the automated version of “start with a two second delay and watch”.

The limits from step 2 still apply. It needs a page cache to fill, it cannot reach pages excluded from caching such as carts, checkouts and logged in views, and it does not make an already warm page faster. If your uncached render is slow enough that warming feels essential rather than useful, the render is the thing to fix, which is speed optimisation work rather than a caching setting.

Where it earns its place is the case this article opened with: a site whose cache goes cold faster than its visitors refill it. Publish, purge or deploy, and the rebuild happens on its own before anyone arrives to wait for it.

How to check it worked

Request a page you just warmed and read the response headers. Those are the notes a server sends back alongside a page, invisible in the browser window, and one of them says whether the answer came from the cache. Depending on what you run, look for x-litespeed-cache: hit, cf-cache-status: HIT, x-cache: HIT, or an age: value above zero, age being how many seconds ago the stored copy was made. You can see the same thing without a terminal by pressing F12, opening the Network tab, reloading, and clicking the first request:

curl -sD - -o /dev/null https://example.com/a-page-you-warmed/

A hit on your first request after a warm is the proof. A miss means the crawl asked for something different from what a browser asks for: another device variant, another compression variant, a trailing slash difference, or a URL that redirects.

Then time a warm page against a deliberately cold one. A large gap means warming is buying your visitors something real. A small one means your uncached render is already quick, and preloading is a marginal gain you can skip.

When it does not work

The crawl runs but nothing gets cached

Usually the request is being excluded. Every request announces what made it, a string called the user agent, and some caching layers deliberately skip anything that does not look like a browser. Check that, check whether cookies are making the page look personal to a logged in user, and check whether the URLs carry query strings. Also confirm you asked for the whole page rather than just its headers, since the headers-only request (--spider in wget) usually stores nothing.

Warming makes the site slow while it runs

Too many requests, too quickly, on a server with too few PHP workers to serve real visitors at the same time. Increase the delay, go back to one page at a time, cut the list to the pages that matter, and move the run to a quiet hour. A slow warm that finishes beats a fast one that makes the site worse while it runs.

The host blocks the crawl part way through

Rate limiting or bot protection, showing up as 403s, 429s or challenge pages in your status output. Slow down first. If it carries on, add the address you are crawling from to the allowed list in your firewall or Cloudflare, or run the crawl on the server itself so the traffic never passes through the protection at all.

Warmed pages go cold again almost immediately

Either the lifetime is very short or something is purging constantly. Find that trigger before spending more effort warming, because a plugin clearing everything on every post save will empty the cache faster than you can fill it.

Common questions

Is preloading the same as caching?

No. Caching stores the built page. Preloading is a request you make yourself so that storing happens before a visitor arrives. Without a cache, preloading achieves nothing at all.

Will preloading improve my PageSpeed score?

Only the server response part, and only indirectly. If Google fetches a cold page it records the slow response; if it fetches a warm one it does not. Preloading makes the warm outcome more likely. It has no effect on layout shift, JavaScript execution or anything else after the HTML arrives.

How often should I preload?

Ideally never on a schedule and always after a purge. A fixed schedule either wastes work on pages that were already warm or misses the purge that mattered. If you must schedule it, pick a quiet hour and keep the list short.

Should I preload a WooCommerce shop?

Warm the shop page, category archives and products, and leave cart, checkout and account alone, since those are excluded from caching for good reason. Watch the frequency too: stock and price changes purge products regularly, and you can end up in a constant rebuild loop.

Deciding whether it is worth it on your site

The whole question is one comparison. How often does a page go cold, and how often does someone visit it? If pages go cold faster than visitors arrive, warming is worth doing, and the lower your traffic the more true that gets. Otherwise preloading spends capacity solving a problem you do not have.

The cases nobody should argue about are the deliberate ones: after a deploy, after a bulk edit, before a campaign. You emptied the cache knowingly, so warming it back up before anyone else arrives costs minutes and removes the risk.

Whatever you decide, set the cache lifetime properly and stop purging everything for a single edit first. Those two changes cut how much warming you need at all, which beats automating work that did not have to happen. If you would rather someone looked at the caching setup with you, get in touch.