The WordPress Page Cache Header Lies. Here Is What To Check Instead..

A caching plugin can be active, configured and doing nothing. Two commands separate a cache that runs from one that only looks like it does.

Aditya Sharma·4 min read

Short answer: do not trust the plugin. Request the page normally, then request it again with a random query string so no cache can serve it, and compare the times. A working page cache shows a gap of several times. If the two are the same, nothing is being cached, whatever the dashboard says.

The wordpress page cache header is worth reading too, but it comes second, and on a lot of WordPress stacks it is not there at all.

The install that taught me to stop trusting settings

I set up a WordPress site this week with a caching plugin on it and went through the usual list. Everything checked out.

Then I looked for evidence the cache had ever stored anything.

Table listing five configuration checks that all passed: plugin activated, WP_CACHE true, advanced-cache.php present, cache directory exists, pretty permalinks enabled. Below them two behavioural checks failed: zero cached HTML files on disk, and identical cached versus bypassed response times
Five green signals and two red ones. The five green ones are what a dashboard shows

you. The two red ones are what a visitor experiences. I never did work out the cause on this install, and I have left that in on purpose, because the lesson is not the cause.*

Every configuration check passed. The cache had never written a single file.

If I had reported on that site from the admin screens, I would have told a client their caching was fine. It was not fine. It was absent.

The test that cannot be fooled

Two requests. The second one carries a query string nothing has ever requested, so no cache can have a copy.

# as a normal visitor
curl -s -o /dev/null -w '%{time_starttransfer}\n' https://example.com/

# same page, cache bypassed
curl -s -o /dev/null -w '%{time_starttransfer}\n' "https://example.com/?n=$RANDOM"

Run each three times, because one sample is noise.

Terminal output showing three normal requests at 0.038, 0.036 and 0.029 seconds, then three cache-bypassed requests at 0.031, 0.026 and 0.027 seconds, with a comment noting the times are identical so no cache is in the path
The two groups are indistinguishable. On a working page cache they are not close,

because the bypassed request has to boot WordPress, run the theme and hit the database, while the cached one reads a file off disk.*

What you are looking for is a gap. Several times over, not a few milliseconds.

Under roughly 1.5x on a real site and I stop believing there is a page cache in front of it.

Reading the WordPress page cache header, and knowing its limits

Headers are quicker than timing when they exist. The catch is that plenty of WordPress page caches, especially disk-based plugin caches, emit nothing to identify themselves.

curl -sI https://example.com/ | grep -iE 'x-cache|cf-cache-status|age|x-litespeed-cache'

A hit looks like x-cache: HIT, or cf-cache-status: HIT, or an age value above zero.

One header to be careful with: cache-control. Your own site writes that one. It says what you would like caches to do, and proves nothing about what any of them did.

I found a live example of that gap while measuring for the companion piece on TTFB. Our own marketing site sends a one-year s-maxage and Cloudflare still answers DYNAMIC. The instruction is being sent. It is not being followed.

Ranked table of ways to verify a WordPress page cache. High trust: compare cached versus bypassed timing, count cache files on disk, read a cache-status header. Medium trust: confirm logged-in requests bypass the cache. Low trust: the plugin says caching is enabled, the drop-in file exists, cache-control is present
Sorted by whether the check observes behaviour or reads configuration. Everything in

the bottom half can be true on a site that caches nothing at all, which is exactly the site I had in front of me.*

The one that costs real money

There is a second failure that runs the other way, and it is worse.

A cache that is working, on a page that should never be cached.

Cart, checkout and account pages have to be excluded, or one customer sees another customer’s basket. Most cache plugins exclude WooCommerce pages by default, and most of the time that default survives.

It does not always survive a hosting migration, a new CDN rule, or somebody adding a page-rule that was meant to fix something else.

Test it by requesting a cart URL with and without a login cookie, and confirming you never get a cached response on either.

Why this ends up on the never-done list

The check takes a minute. It needs redoing after every hosting change, every CDN change and every cache plugin update, on every site you look after.

So it gets done once and then trusted forever, which is the same trap as the scheduled jobs nobody notices have stopped and the backup file left in the web root.

Protuno’s free audit reads the caching headers from the domain today, so you can see at a glance which sites in a portfolio are announcing a cache and which are not. Being straight about the rest: Dash, the Performance agent that would re-run the timing comparison on a schedule, is built and named but not live yet.

The two curl lines above are the whole method in the meantime. Run them on the client site you are most confident about. That is the one worth checking.

Comments