If I can’t effectively run the preloader on my Kinsta clients? Fine. I will build my own.
I love Kinsta. I put all my clients on it. I put my wife’s hobby site on it. Ryzodus is on it.
It’s fast, effective, and has incredible tools.
Just one problem. My favorite cache plugin doesn’t run its preloader effectively on it. Kinsta has a whole page of blah blah blah explaining why. I won’t get into that. Instead, I built my own cache warmer.
Short version: if PageSpeed Insights shows a lab TTFB under 100 ms and a field TTFB near two seconds on the same page, your cache is cold for most real visitors. Every purge resets it, and the person who pays the rebuild cost is whoever shows up next. On a long-tail site that person doesn’t arrive for hours or days, so the page just sits there cold. I measured this on a travel client’s WordPress site, built a throttled warmer to fix it, and then spent a while being fooled by my own report of the results.
This is not a setup guide. It’s a diagnosis. If you’re on Kinsta with WP Rocket and your field TTFB in PageSpeed Insights is bad while lab TTFB is fine, this is probably your problem, and the post is written so you can paste it into your assistant of choice alongside your own numbers and ask what applies.
Symptoms
The site: WordPress 7.x on Kinsta, Elementor Pro, JetEngine, WP Rocket with Remove Unused CSS on, about 250 tour pages plus destination guides. A partner in Argentina emailed on 2026-08-11 that the site was “really slow.” I’d spent the previous week on page weight, image handling, and JavaScript, and had lab numbers to show for it. None of that work had moved a single field metric.
The Chrome UX Report baseline from 2026-08-05 was the clue I didn’t read properly at the time:
| Metric (p75) | Mobile | Desktop |
|---|---|---|
| TTFB, field | 1,848 ms | 1,448 ms |
| TTFB, lab | 30 to 90 ms | 30 to 90 ms |
| LCP, field | 2,541 ms | 1,755 ms |
Lab TTFB was fine because lab tests hit a page I’d just been looking at. Field TTFB was terrible because real visitors hit pages nobody had looked at since the last purge. I logged the gap as “not explained” and went back to images. That’s on me.
What made it misleading
Every cache header I checked said the cache was working. Cloudflare answered cf-cache-status: HIT with an incrementing Age. Kinsta answered x-kinsta-cache: HIT. The origin was fast. All true, for the pages I was checking, which were the pages I’d just warmed by checking them. A cache is a queue, and someone has to stand at the front of it. When you test a site by hand, that someone is you, and you never see the cold state.
The three hypotheses I tested first all died on evidence. The mega menu wasn’t AJAX-loading (all 48 destination links were inlined in the markup). The South American edge wasn’t cold (a São Paulo WebPageTest run gave TTFB 194 ms, LCP 1.27 s). The destination pages weren’t missing their optimised CSS (they had it; my first query said otherwise because WP Rocket stores the URL without a trailing slash and my LIKE pattern had one). Three good ideas, all wrong, and I’m glad I wrote them down, because the fourth one is the whole story.
Investigation: fetch the top 150 pages and count the misses
Here’s where “we” starts meaning me and the engineering harness I work in, since we each saw things the other didn’t. We pulled the top 150 pages by GA4 traffic and fetched each one twice, once with a desktop Chrome user agent and once with a mobile one, 2.5 seconds apart, logging the x-kinsta-cache header and the time to first byte for each request. That’s it. No tooling beyond bash and curl.
300 requests (150 URLs x 2 devices), 0 errors
216 MISS · 84 HIT 72% of top-traffic pages were cold
MISS avg TTFB 1.16 s
HIT avg TTFB 0.32 s about 0.84 s penalty before any HTML arrives
desktop 84/150 MISS (56%)
mobile 132/150 MISS (88%)
Seventy-two percent of the pages people actually visit were cold, and a cold page cost about 0.84 seconds before the first byte. That number lines up with the field TTFB gap almost exactly. The week on page weight had been working on the wrong problem.
The mobile number is the one I keep coming back to. Kinsta’s page cache and WP Rocket’s used-CSS are both keyed on URL and device (I verified this later: desktop MISS, then mobile MISS on the same URL, then desktop HIT). My own browsing and testing is desktop-first. So my day-to-day use of the site was quietly keeping the desktop cache half warm, and doing nothing at all for mobile. If your mobile field metrics trail desktop and you can’t explain why, check this before you check anything about images.
Root cause: nobody owns what happens after a purge
I change layouts and content on this site once or twice a week and purge cache each time. WP Rocket’s answer to the cold-after-purge problem is its preloader, which crawls the sitemap and rebuilds every page. On this host that’s not an option: it crawls uncontrolled against a three-worker PHP pool, and it took the site down twice in early August 2026 (Kinsta’s guidance is the same: leave it off). Kinsta’s answer is edge caching, which only helps a page that is already warm, plus the assumption that visitors warm the rest.
That assumption is the seam. On a site with 250 tour pages and a purge or two a week, most of the long tail doesn’t get a visitor between purges. So it’s served cold to a real person every single time, and because CrUX only counts real Chrome users, every one of those cold hits lands in your field data. Neither vendor is wrong. Nobody’s in charge of the gap between them.
Fix: a warmer that does the preloader’s job slowly, on purpose
The warmer is the measurement script with a sleep in it. The rules that matter:
- Sequential, 2.5 seconds apart. About 24 requests a minute, below normal organic traffic. The outage that killed the preloader idea was 22 to 42 requests a minute with 15-second stalls.
- Backoff. Any response over 8 seconds doubles the delay, capped at 15. The script protects the site from itself.
- Both devices, every URL. Warming desktop only does half the job, and it’s the half you were already doing by accident.
- It runs from my machine, not the client’s server. No cron on the host, nothing to survive a platform migration, and an off switch that’s just closing the terminal.
- Log the cache header and the TTFB per request. Coverage is measured, not assumed.
Cost: zero. It’s curl. No API, no model, no units. The only real expense is the PHP time on the client’s server to build each cold page, and that’s the whole point: that time gets spent on a bot instead of on a visitor.
Where I fooled myself
I wired the warmer into a local dashboard with a tile that reported the miss percentage of the last run. For weeks the pattern was: purge, wait a few minutes, click warm, see 50 to 70% MISS, click warm again, see 0%. I read that as the warmer being flaky on the first run. I built the tile, so this one’s mine too.
It wasn’t flaky. A MISS on the warming request is the warmer working: the page was cold, and from that fetch on it’s cached. The tile was answering “how cold was it when I arrived,” which is a fine number, and presenting it as a failure rate. The second run reading 0% was the proof the first run worked, and I’d been reading it as the first run finally succeeding.
There was one real mechanism hiding under the misread, and it’s specific enough to be worth quoting. WP Rocket’s Kinsta integration (inc/ThirdParty/Hostings/Kinsta.php, as of WP Rocket 3.x in Sept 2026) subscribes rocket_saas_complete_job_status to clean_kinsta_cache_url. In plain words: every time a Remove Unused CSS job finishes for a URL, the page is purged from Kinsta’s cache so the next request gets the optimised version. Sensible. But after a “Clear Used CSS,” every page has a pending job, so pages I’d warmed early in a run were being purged again minutes later as their jobs landed. The first run wasn’t failing. Some of it was being undone behind me.
The fix was to make the warmer answer both questions. It now runs two passes and the log carries a column for which one. The warm pass reports “found cold.” Then a verify pass re-fetches everything at one second spacing (a HIT never touches PHP, so this is cheap, and any MISS it meets re-warms the page). The verify pass reports “still cold after,” which is the number that actually matters. The tile shows both, and it only goes amber when more than 5% is still cold after verifying, because that means the site isn’t holding its cache and someone should look. The general rule I took from this: a monitor that reports the state it found, instead of the state it left, will train its reader to distrust it.
One more thing I got wrong on the way and want on the record. WP Rocket has a “cache lifespan” setting (10 hours by default) and I briefly blamed it for pages going cold on their own. It’s a no-op on Kinsta. The scheduled purge deletes WP Rocket’s own cache files, and Kinsta’s MU plugin sets do_rocket_generate_caching_files to false so those files never exist. The cache directory holds one placeholder index.html. If you’re on Kinsta and staring at that setting, it does nothing. Kinsta’s real purge record is logs/cache-purge.log on the host, and that’s where I found the actual answer to “who cleared it”: an Elementor save, which WP Rocket’s Elementor integration turns into a full-domain purge under some conditions.
What the field data did
Warm runs started 2026-08-12. In Search Console’s Core Web Vitals report, desktop went from 0 good URLs to about 400 good URLs over the following three weeks, with the big step around Aug 25. Mobile’s poor-URL count fell from about 250 to 76 on the Sept 1 data point, with those URLs moving to “needs improvement” rather than “good.”
I’m showing that as an observation, not a proof. Three things I’d want a sceptic to know: the report is a 28-day rolling window, so the timing of the Aug 25 and Sept 1 steps fits a change on Aug 12, but a smaller desktop step on Aug 13 does not and I can’t explain it. Other work landed on the site in the same weeks (redirect cleanup, a WP Rocket fatal that got patched on Aug 19, structured data in early September). And mobile still has zero good URLs, because a warmer fixes TTFB and mobile’s remaining problem is LCP and interaction, which a warmer can’t touch. The number I actually trust is the 0.84 seconds from the Aug 11 run, because that one is causal by construction.
What I’d check first next time
- The lab-versus-field TTFB gap in PageSpeed Insights. Both numbers are on the same screen. Lab under 100 ms and field over a second is a cold cache until proven otherwise.
- Fetch your top 50 pages with curl, twice each, and count the cache-status header. Use a desktop and a mobile user agent. On Kinsta the header is
x-kinsta-cache; on WP Engine it’sx-cache; behind Cloudflare it’scf-cache-status, which for HTML will usually say DYNAMIC and tell you nothing. Do this before you touch an image. - Compare your purge frequency to your long tail’s visit frequency. If pages go longer between visitors than you go between purges, they’re cold for real people every time. Size doesn’t matter; the ratio does. A 20-page site that publishes daily has this problem too.
- If you warm, throttle, back off, do both devices, and verify. And read the second number, not the first.
Debriefs are drafted from live working-session context by the engineering harness that did the work, then reviewed, verified, and published by me.