Let me tell you about the most frustrating debugging session I’ve had in a while.
I had done everything right. The performance stack was solid:
- WP Rocket for page caching and frontend optimization
- Redis Object Cache for database caching
- Varnish Cache at the server level
- PHP OPcache enabled
- Nginx web server
This should have been a fast WooCommerce store. It wasn’t.
Something Was Off
Pages were taking 4–6 seconds on first load. Annoying, but acceptable for a cold cache. What bothered me was the repeat visits — 3 to 4 seconds even after the page should have been cached and served instantly.
That number didn’t make sense.
WordPress Site Health was equally unimpressed:
Page cache is detected but the server response time is still slow.
Median server response time: 1,600ms. WordPress recommends under 600ms.
But the headers were what really caught my attention. I refreshed the page. Refreshed again. And again.
Every single time:
cache-control: no-store, no-cache, must-revalidate
x-cache: MISS
age: 0
The cache wasn’t being used at all. Not once. Classic WooCommerce cache not working situation — except nothing obvious pointed to why.
Okay, So WP Rocket Is Broken. Right?
That was my first thought. WP Rocket not caching pages is the usual suspect when you see a persistent x-cache: MISS. I checked whether it was actually generating cache files:
find wp-content/cache/wp-rocket
Files were there. Plenty of them.
WP Rocket was doing its job fine. I was wrong about that.
Next suspect: Redis. I pulled up Query Monitor.
Object Cache Hit Rate: 96%
12,148 hits — 477 misses
Redis was working beautifully. Not the problem either.
Varnish? Configured correctly, with the standard WooCommerce exclusions for /cart, /checkout, and /my-account.
So everything was working. And the cache was still being missed on every single request. I’ll be honest — at this point I sat back and just stared at the screen for a minute.
Then I Looked at the Cookies
I went back to the response headers and looked more carefully this time. That’s when I spotted it — buried in the headers on the homepage, on a product page, on the blog — everywhere:
set-cookie: wp_woocommerce_session_xxxxxxxxx
A WooCommerce session cookie. Going out to every visitor. Before they’d added anything to the cart. Before they’d logged in. Before they’d done anything at all.
That was the problem.
This is a well-known WooCommerce page cache bypass trigger. When Varnish sees a wp_woocommerce_session cookie being set in the response, it immediately assumes the content is personalized and dynamic — so it skips caching entirely. Which is exactly what Varnish is supposed to do. The problem wasn’t Varnish’s logic. The problem was that the cookie had no business being there in the first place.
Every visitor. Every page. Fresh uncached response. No exceptions.
Finding the Plugin Breaking the Cache
Now I needed to know which plugin was doing this. A lot of WooCommerce plugins are capable of this — currency switchers, cart abandonment tools, quote request plugins, B2B extensions. Any plugin that initializes WC()->session or calls WC()->cart too early can trigger this exact WooCommerce cache bypass.
I ran a search through the plugins folder:
grep -R "set_customer_session_cookie" wp-content/plugins --include="*.php"
The answer was inside the Addify B2B Request a Quote module. Two functions, both hooked into wp_loaded — meaning they ran on every page load, for every visitor:
public function afrfq_start_customer_session() {
if ( isset( WC()->session ) ) {
if ( ! WC()->session->has_session() ) {
WC()->session->set_customer_session_cookie( true );
}
}
}
There it was. The plugin was forcing WooCommerce to create a session for every anonymous visitor the moment WordPress loaded — before anything else happened on the page.
To be fair, I get why a developer might write this. Quote request features need session data. But initializing that session for every visitor on every page, regardless of whether they ever touch the quote feature? That’s the kind of thing that quietly kills performance and leaves you chasing ghosts for hours.
The Fix
I disabled the Request a Quote module temporarily.
The wp_woocommerce_session cookie disappeared from the headers immediately.
Then I ran:
curl -I https://mywebsite.com/
curl -I https://mywebsite.com/
First request: x-cache: MISS — expected, cold cache.
Second request: x-cache: HIT, age: 20.
I actually said “finally” out loud.
I then measured actual response times:
curl -o /dev/null -s -w "%{time_total}\n" https://mywebsite.com/
0.054
0.056
0.050
0.082
0.049
50 to 80 milliseconds. Down from 4 to 6 seconds. The entire caching stack — WP Rocket, Varnish, Redis — was now working together the way it was always supposed to.
What I Took Away From This
The tools weren’t broken. WP Rocket was generating cache files all along. Varnish was following its rules correctly. Redis was doing its job on the backend. None of them were the problem.
One plugin, one wp_woocommerce_session cookie, and the entire caching stack was rendered useless for every visitor.
This is more common than you’d think. If you’re wondering why your WooCommerce site is slow even with caching enabled, or why WP Rocket doesn’t seem to be working despite showing cache files — the culprit is often a plugin that’s initializing WooCommerce sessions too early. Quote requests, currency switchers, cart abandonment trackers, loyalty programs — a lot of them do this without any obvious warning. There’s nothing in your WordPress dashboard that tells you it’s happening.
So before you upgrade your server, add PHP workers, or switch hosts — spend 60 seconds checking your response headers. If you see wp_woocommerce_session_ on a page where no shopping has happened, that’s your starting point. It might save you a very long afternoon.
