WooCommerce Cart Caching: One URL, Two Customers, One Very Bad Morning.
We requested the same cart URL as two customers. One had a product and a total, the other had nothing. Cache that page and they see each other’s.
Short answer: woocommerce cart caching is safe by default because WooCommerce marks the cart, checkout and account pages private, no-cache and the Store API no-store. Leaks happen when something downstream ignores those headers. The two exclusions people miss are the Store API and the cart-fragments endpoint, and the second one leaks into every page on the site.
Proving the cart really is per-customer
Before arguing about exclusion rules, it is worth seeing how different the same URL is for two people.
I requested /cart/ twice with separate cookie jars, seconds apart, on the same store.

different Store API payloads. Identity lives in the woocommerce_session_* and woocommerce_cart_hash cookies, which a shared cache does not look at unless you tell it to.*
Cache that URL and one of those two people gets the other’s page.
WooCommerce is not the problem here
This surprised me slightly when I checked it, because cart leaks get blamed on WooCommerce often enough that I expected the defaults to be weaker.

unstoreable, and sets a 1984 expiry for caches too old to understand the rest. When a cart leaks, something downstream decided to override this.*
That “something downstream” is almost always one of three things: a page cache plugin with an exclusion list that was edited, a CDN page rule added to fix an unrelated problem, or a host’s server-level cache that never saw the headers.
The two exclusions that get missed

fragments endpoint is requested from every page on the store to keep the header cart icon current, so a cached fragment shows a stranger’s basket count on the homepage.*
If you take one thing from this: an exclusion list made only of page URLs is incomplete on any store using the block cart and checkout.
Testing WooCommerce cart caching on a live store in two minutes
You do not need to place an order.
# 1. as a fresh visitor, take the cart page
curl -s -c jarA -b jarA "https://example.com/?add-to-cart=123" -o /dev/null
curl -s -c jarA -b jarA "https://example.com/cart/" | wc -c
# 2. as a completely separate visitor
curl -s -c jarB -b jarB "https://example.com/cart/" | wc -c
# 3. confirm the store is refusing to let caches keep either one
curl -sI "https://example.com/cart/" | grep -i 'cache-control\|x-cache\|cf-cache-status'
What you want from step three is private or no-store in cache-control, and a BYPASS or MISS from whatever CDN is in front. For comparison, woocommerce.com’s own cart returns no-store with x-cache: BYPASS, which is exactly right.
What should alarm you is a HIT on any of those URLs. That is a shared copy of a personal page.
Then repeat step three against the fragments endpoint:
curl -sI "https://example.com/?wc-ajax=get_refreshed_fragments" | grep -i 'cache-control\|x-cache'
Why this one is worth more than a status check
Most store monitoring answers “is it up”. This failure never makes a store go down. Every URL returns 200 throughout. The store takes orders the whole time.
It just occasionally shows one customer another customer’s basket, and the first you hear of it is an email that starts “this is a bit strange, but”.
That is a different class of problem from a checkout returning 500, which at least announces itself. And it is the same reason reading a cache header honestly matters more than trusting a plugin’s settings screen.
Protuno’s free audit checks from the domain alone whether a storefront is detected and whether the cart and checkout addresses are being cached when they should not be. Straight with you as on every post: Till, the eCommerce agent that would re-check this after every CDN change, is built and named but not live yet.
Run the three commands above on the store you migrated most recently. Migrations are where exclusion lists get lost, along with the scheduled jobs that stop at the same moment and nobody notices either.
Comments