Someone tells you the site was slow. You open it, it loads instantly, and you reply asking whether it might have been their connection. A week later somebody else says the same thing, usually about a page you had just been editing.

You are not imagining the speed and they are not imagining the wait. You were served a page that already existed, saved earlier and kept ready. They were served one the server had to build from scratch. That is what people mean by a cold cache, and it is the most common reason a WordPress site is quick whenever you check it and slow for the people who matter.

By the end of this you will know what your server does when there is no saved copy to hand, when your saved copies get thrown away, how to prove which of the two a visitor got, and what to change so a real visitor stops being the one who waits. The diagnosis only reads what the server says, and the fixes are settings in your caching plugin, not code.

Before you start

You need curl, a command for fetching a URL and showing the raw answer. It ships with macOS and Linux and is built into current Windows, so open Terminal or PowerShell and you have it. You also need WordPress admin and your hosting panel for the fixes. The diagnosis is read only, so none of it can break anything. No backup is needed to change a cache lifetime, though the worst case for a caching change is stale content being served, so make them outside busy hours on a live shop.

Fix one thing before you test anything: log out, or use a private window. Every serious caching setup deliberately skips the stored copies for logged in users, so you always see your own latest edits. That is why your admin session gives you a different experience from a visitor’s. Testing while logged in is not testing the cached site.

Step 1: What a cache hit and a cache miss actually do

Page caching stores the finished page after WordPress has built it once, and hands that stored copy to everyone who asks afterwards. When the copy exists and gets used, that is a cache hit. When it does not and the page has to be built again, that is a cache miss. Switch between the two below to see the difference in what the server actually has to do.

On a cache hit the sequence is short. The server, or a plugin running very early, matches the URL against a stored copy, checks it has not gone out of date, and sends it. Almost no code runs, the database is never opened, and nothing is assembled. A few tens of milliseconds is normal, and most of that is the time the data spends travelling rather than anything your site did.

On a cache miss the whole chain runs:

  1. PHP boots, loads its extensions and reads wp-config.php.
  2. WordPress loads itself. Core, then every active plugin’s main file, then the theme’s functions.php. All of it, whether or not this page uses any of it.
  3. Plugins run their set-up code. WordPress announces a series of moments during start-up, called hooks, and every plugin does its preparation at one: licence checks, translations, registering content types, wiring up features.
  4. The database gets queried. The site’s settings, then the posts for this page, then whatever the theme and plugins ask for on top: menus, widgets, related posts, stock, custom fields. Dozens of separate questions.
  5. The page gets built. WordPress works out which template file covers this page and runs it, with page builders reassembling their layouts as they go.
  6. The result is stored, then sent. Only now does anything at all arrive at the visitor’s browser.

All of that happens before the browser has a single character to work with, which is why the visitor sees a blank screen and not a half-drawn page. On a lean site with decent hosting a miss might be a few hundred milliseconds. On a page builder site with forty plugins on oversold shared hosting it can be seconds. So the fix is usually not “make the site faster”. The site already is faster, for everyone except whoever arrived first.

Step 2: Know when your cache goes cold

Caches go cold at specific moments, and knowing them tells you when your visitors are exposed.

After you publish or update a post. Saving purges that URL, meaning it throws away the stored copy so a fresh one gets built, and usually does the same to the homepage, the archives and the feed. That is correct behaviour, because otherwise nobody would see your change. It also leaves all of those pages cold.

After a plugin, theme or core update. Most caching plugins clear everything, assuming markup may have changed. Updates happen in batches, so the whole site goes cold at once.

After a manual purge. The Clear Cache button is the biggest cause of self inflicted cold pages. People press it out of habit, emptying the entire store to fix one page.

When the TTL expires. TTL means time to live: how long a stored copy is kept before it is binned as too old. Your caching plugin calls it the cache lifetime. If yours is an hour, any page nobody has requested in the past hour is cold. On a hundred page site with modest traffic, that is most of the site most of the time.

Now consider who lands on one. Not you, because you are logged in and looking at pages you just touched. It is a first time visitor from a search result, on mobile, on the page you edited an hour ago, with nothing of your site already on their phone and no opinion of your business yet. The visit where a slow start does most damage.

Step 3: Prove it with response headers

Response headers are the labels a server attaches to every page it sends. You never see them on screen, but caching layers sign their work there, and they give a straight answer about hit or miss. Run this in Terminal or PowerShell, replacing the URL with your own:

# -s quiet, -D - dumps the response headers, -o /dev/null throws away
# the HTML so the output stays readable.
curl -sD - -o /dev/null https://example.com/the-slow-page/

Use it as written rather than curl -I. The -I version asks for the labels only and not the page, and some caching layers treat that differently or skip the cache for it entirely, which gives a misleading answer.

If a command line is not for you, your browser shows the same thing: press F12, open the Network tab, reload the page, click the first row and read the Response Headers panel.

Look for a header naming your cache layer. There is no single standard, but these are common:

  • x-litespeed-cache: hit on LiteSpeed servers, which covers most cPanel hosts running LiteSpeed.
  • cf-cache-status: HIT, MISS, EXPIRED, DYNAMIC or BYPASS when Cloudflare is in front of your site. DYNAMIC means Cloudflare is not storing your pages at all, which is its default for WordPress.
  • x-cache: HIT or MISS from Varnish and several managed hosts.
  • age: in seconds, telling you how long the stored copy has been sitting there. An age of 0 on every request means nothing is being kept.

Some plugins set no header. WP Rocket and WP Super Cache instead write a hidden note at the end of the page’s source recording when the copy was made. Right-click, choose View Page Source, scroll to the bottom, then reload and look again. If that timestamp changes every time, you are getting a miss every time.

No header and no comment is itself the finding. Nothing is storing your pages, and every visitor runs the full chain from step 1.

Step 4: Time the first load against the second

Headers tell you hit or miss. Timing tells you what a miss costs. The number that matters is time to first byte, usually shortened to TTFB: the gap between asking for the page and the very first character of it arriving. It is the part of the wait your server is responsible for, before the browser has anything to draw.

# time_starttransfer is time to first byte: everything the server did
# before sending a single character. Run it twice in a row.
curl -s -o /dev/null -w "TTFB: %{time_starttransfer}s\n" https://example.com/the-slow-page/
curl -s -o /dev/null -w "TTFB: %{time_starttransfer}s\n" https://example.com/the-slow-page/

The first should be slower, because it is the one that built the page and stored the copy. The second gets that copy. For a clean test, clear that single page from the cache first, then run the pair.

Watch the gap between the two rather than the absolute figures, which depend on where you are sitting. A cold page at over a second and a warm one in the tens of milliseconds means a first time visitor from Google wears the whole difference. If both requests are equally slow, nothing is being stored, and that is a configuration problem rather than a warming one. If both are fast and the complaints continue, the cause is after the HTML arrives, which is the ground I cover in speeding up WordPress and fixing Core Web Vitals.

Step 5: Read it in PageSpeed Insights

PageSpeed Insights tests any URL you give it, and its lab results (the part generated on the spot, rather than the field data from real Chrome users) include a check called “Document request latency”, which older reports called “Reduce initial server response time”. That is the same TTFB you just measured, for whichever state the page was in when Google fetched it, which is why the same page tested twice can report two very different figures. If that number bounces around with no code changes in between, you are watching cold and warm hits, not an unstable server.

Step 6: Set a TTL that suits your traffic

Several popular plugins default to a lifetime measured in hours, which is far too short for a low traffic site. If a page is binned after an hour and gets one visit a day, every visitor gets a cold load and the cache helps nobody. You will find the setting in your caching plugin, labelled cache lifetime, cache expiry or TTL.

The rule I use: a cached page should live longer than the typical gap between visits to it, and no longer than you would tolerate serving something stale.

  • Brochure sites, service pages, blog archives: days is fine. Nothing changes on its own, and any edit purges the page anyway.
  • News, listings, anything that changes without you touching it: hours.
  • Cart, checkout, account and anything personalised: never cached. Most layers exclude these already. If yours does not, exclude them before anything else.

Long lifetimes are safer than they sound, precisely because publishing an edit clears the affected pages anyway. The TTL is a backstop for pages nothing has touched, not the thing keeping your site current.

Step 7: Stop purging everything for one change

Fix a typo on one page, clear the whole cache, and you have made every page cold to fix one. Do that at your busiest hour and the next few dozen visitors all take the slow path.

Clear narrowly instead. Most caching plugins put a “clear this page” option in the black admin bar while you are looking at the page itself, and that plus the automatic clearing that happens when you save covers nearly every edit. Save full purges for changes that really do affect every page: a theme switch, a header edit, a new global stylesheet. Those are the moments to warm the cache back up afterwards.

The opposite failure exists too. Some host level caches purge only the edited post’s own URL and leave the homepage and archives showing the old title, which is why I wrote a separate plugin for 20i’s StackCache. Check what yours clears when you save a post.

Step 8: Warm the cache by hand

Warming means loading a page yourself after the cache has been cleared, so a fresh copy gets stored before a real person asks for it. You take the slow load instead of them. The server neither knows nor cares that the request came from you rather than a visitor. After a full purge the pages that matter are the homepage, your main service or product pages, and whatever is getting search traffic, which is usually a short list:

# Request each URL in turn so the cache stores a fresh copy. --compressed
# matches what a browser sends, so you warm the variant a visitor gets.
# The sleep is politeness: an empty cache is the moment your server is
# least able to cope with a burst.
for url in \
  "https://example.com/" \
  "https://example.com/services/" \
  "https://example.com/blog/the-post-i-just-published/"
do
  curl -s -o /dev/null --compressed "$url"
  sleep 1
done

If a loop like that is not your thing, opening each page in a private window does the same job. Then confirm with the header check from step 3. A hit on the next request means it worked.

That is a fine habit after a deliberate purge. It stops being fine when purges happen on their own several times a day, triggered by scheduled posts, stock updates, comment approvals and plugin updates. You will not be at a keyboard for most of them.

Warming automatically, so you stop doing it by hand

That gap is why I wrote ABCode Cache Warmer, free on wordpress.org. It does the same job as the loop above, except it reacts to the purge: when the cache is cleared it starts rebuilding the affected pages straight away, so the copy a visitor lands on already exists.

The curl loop above works, but only while you remember to run it, and nobody remembers at eleven at night after a content update.

  • Works alongside the caching plugin you already have, including WP Rocket, LiteSpeed and W3 Total Cache.
  • Reads the cache headers back and reports what happened per URL, the same evidence you gathered by hand in step 3.
  • Measures your response time and server load to decide how hard to push, since the point is not to flood an empty cache.
  • You choose the scope: posts, archives, the sitemap, or your own list of URLs.

It is a warmer, not a cache, so if step 3 showed nothing is storing your HTML, sort the caching out first. It cannot help logged in traffic, carts or checkouts, which are excluded from page caching by design, and it does not make a hit faster, only more likely. Whether your site needs warming at all is a fair question, and I work through it in cache preloading in WordPress, and when it helps. If it does, warming on purge happens whether you thought about it or not, which is the only version of this that survives contact with a real week.

How to check it worked

Purge one page deliberately, then request it and read the headers. However you warmed it, your first request as a visitor should be a hit, not a miss. That is the whole test.

Then repeat the timing pair from step 4 on a page you have not touched for a day. If cold and warm are now close together, your TTL change is holding pages between visits rather than letting them expire unused.

When it does not work

Every request comes back as a miss

Something is stopping the copy being kept. Usual causes: a cookie being set on every request (caches skip storing a page when the server looks like it is tracking someone individually), a query string the cache treats as a unique page, a plugin sending Cache-Control: no-cache, or the URL sitting on your plugin’s exclusion list. Check the exclusions first, then request the URL with nothing after a question mark and compare.

It is a hit for me and a miss for a visitor

Caches can store separate copies per device type or consent state, so you warmed the desktop version and your visitor is on mobile. Warm both if your setup splits them.

Pages go cold again within minutes

Something is clearing more often than you think: a plugin emptying everything whenever any post is saved, a stock sync on a schedule, or a very short lifetime. Watch the age header for a few minutes. It counts the seconds since the stored copy was made, so if it keeps resetting to 0, find the trigger before warming anything.

Common questions

Is a cold cache the same as a slow site?

No, and confusing them sells a lot of unnecessary hosting upgrades. A cold cache means the first request after a purge is slow and the rest are quick. A slow site is slow every time. The timing pair in step 4 separates them in ten seconds.

Why is my site fast for me but slow for visitors?

Three things stacked. You are logged in, so you may be bypassing the cache entirely. You already have the CSS, JavaScript and fonts. And you mostly visit pages you just edited, which are either warm from your own visit or cold from your own purge.

Do I need this if my host handles caching?

Some managed hosts warm the cache after a purge themselves. Purge a page and see whether your next request is a hit. A miss means nobody is warming anything.

Where this leaves you

Nobody could reproduce the complaint because the slow load only exists for whoever arrives first after a purge. Once you know that, the diagnosis stops being opinion: the headers say hit or miss, and the timing says what a miss costs.

The fixes are unglamorous and they hold. Give pages a lifetime that matches how often they are really visited. Purge the page you changed rather than the whole site. Rebuild what you did purge before somebody else has to, by hand for a short list, automatically when the purges outnumber your patience.

If you would rather hand the caching setup to someone who does this often, that is part of my WordPress speed optimisation work, and keeping it right through updates is what a maintenance plan is for. Either way, take the timings first.