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.

Aditya Sharma·4 min read

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.

Table comparing two sessions on the same cart URL. Session A returns 146,427 bytes with one Store API item, a total of 1900 or nineteen pounds, and the product Demo Widget. Session B returns 139,501 bytes with zero items and a total of zero. The difference is 6,926 bytes
Nearly seven kilobytes of difference between two visitors on one URL, plus completely

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.

Table of cache headers a stock WooCommerce install sends per URL. The homepage and shop page send none. Cart, checkout and my-account send Cache-Control no-cache, must-revalidate, max-age=0, private plus an Expires date of 11 January 1984. The Store API cart endpoint sends Cache-Control no-store
Out of the box WooCommerce marks every personalised surface as private and

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

Table of what must be excluded from caching on a WooCommerce store. The cart, checkout and account pages are excluded by default in most plugins. The Store API path and the wc-ajax get_refreshed_fragments endpoint often are not. A rule bypassing the cache whenever a WooCommerce session cookie is present is listed as the safest approach
Row three is the one that turns a contained problem into a site-wide one. The cart

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