# WooCommerce cache serving the wrong country’s price

The dangerous failure of a cached WooCommerce shop is not a slow page. It is a fast one that carries the previous visitor’s country: a Portuguese…

Source: https://webkodingtheme.com/blog/woocommerce-cache-serving-wrong-country-price/

---

Tax and compliance

# WooCommerce cache serving the wrong country’s price

AÖ Arif Özelci · Published 22 Sept 2026 · 7 min read
The dangerous failure of a cached WooCommerce shop is not a slow page. It is a fast one that carries the previous visitor’s country: a Portuguese customer sees the German price on the product page, then a different total in the cart. Here is a test that catches it before launch — two browser profiles, a fixed order of requests, and the cache header at every layer.

## What you are actually looking for

A load test tells you whether the shop survives traffic. It tells you nothing about whether the traffic saw the right numbers. When a page cache sits in front of a shop whose prices depend on the customer’s location, the first visitor to a URL decides what everyone after them sees until the entry expires. If that visitor was in Germany, the page is now the German page — 19% VAT baked into every price — and it is served, quickly and with a cache HIT, to the next visitor from Portugal, who owes 23%.
The customer only finds out in the cart. The cart is never cached, so WooCommerce computes it fresh, with the real location, and the total no longer matches the price they clicked. That is the support ticket you get: “the price changed when I added it to the basket.” The cache did exactly what caches do. It just was not told that the page varied.
If you have not yet decided how to structure the cache, read caching a WooCommerce shop when the price depends on the customer’s country first. This article assumes you have built one of those and want to know whether it works.

## The setup: two profiles, not two tabs

Use two separate browser profiles, one standing in for each country. Not two tabs, and not one normal window plus one incognito window — the point is that neither profile has the other’s cookies, and a WooCommerce session cookie in particular changes how most cache plugins behave. Many of them bypass the cache the moment they see a wp_woocommerce_session_ cookie, which means a test run from a browser that has ever added something to a cart is testing the uncached path and will pass no matter what.
The country signal has to arrive the way it arrives in production. If your cache is keyed on the CDN’s country header, or WooCommerce geolocates from the IP address, then a cookie or a query string that tells staging “pretend I am in Portugal” tests a different system from the one you are shipping. Use a VPN exit in each country, or two small servers, one in each. If that is not available, inject the override at the same point production reads the country — the CDN header, not a WordPress filter — so every layer below it sees exactly what it would see for a real visitor.
Do it on staging with the same cache configuration as production. A test against a staging site with the cache plugin disabled tells you your tax rules are right, which you already knew.

## The sequence, and why the order matters

- Purge the cache at every layer — plugin, server, CDN.
- In the Germany profile, open a product URL. This warms the cache. Note the displayed price and the cache status of the response (it should be a MISS).
- In the Portugal profile, open the same URL. Note the price and the cache status. If it is a HIT and the price is the German one, you have found the failure.
- Purge again. Repeat with the order reversed: Portugal first, then Germany.
- Compare each displayed price with what your configured tax rules say it should be. Not with the other profile — with the rule. Both profiles showing the same number is a failure if the rates differ; both showing different numbers is still a failure if one of them is wrong.
Reversing the order is not thoroughness for its own sake. A cache that is not keyed on country but whose default location happens to be Germany will pass the Germany-first run — the German page is the right page, and the Portuguese visitor’s leak is what you catch on step 3. But some setups have a second cache, keyed on something like the base country, that only misbehaves when the warming visitor is not in the default location. Running both orders costs two minutes and covers both.

## Read the cache status at every layer, not just the last one

A typical stack has three caches in a row, and each reports separately. Open the browser’s network panel, select the document request, and read the response headers:
- Cloudflare: cf-cache-status — HIT, MISS, DYNAMIC (not eligible for caching), BYPASS or EXPIRED.
- LiteSpeed: x-litespeed-cache — hit or miss. With ESI in use, the page can be a hit while the price fragment is served separately.
- Varnish: usually X-Cache and X-Cache-Hits, though the exact header names depend on the VCL the host wrote.
- WP Rocket and similar plugins: no header — look at the very end of the page source for the plugin’s HTML comment, which includes the time the cached copy was generated.
- Any of them: an Age header greater than zero means somebody upstream served a stored copy.
The layers can disagree, and the disagreement is the diagnosis. Cloudflare says DYNAMIC and the price is still wrong: the leak is in the plugin or the server cache. Plugin comment says the copy is fresh, Cloudflare says HIT: the plugin is doing the right thing and the CDN is serving the previous country over the top of it. And if you have configured a Vary or a cache key on country, the second profile’s first request must be a MISS. A HIT there means the key is not being applied to this URL.

## Not only the product page

The product page is where people look, so it is where the exclusions and the workarounds tend to have been applied. The other pages that print prices are separate cache entries with their own rules, and it is common for the product page to be handled correctly while the pages that lead to it are not. Repeat the same two-profile sequence for:
- The home page, if it shows featured or recent products with prices.
- The shop page and at least one category archive.
- Search results.
- A product page’s related-products and upsell blocks — these are part of the same cached document, but their prices come from a different code path than the main product’s.
- Any page with a mini-cart in the header that shows a subtotal.
If you are on the JavaScript-fetch approach, where the page is cached once and the prices are filled in by a request per page, check that the endpoint itself is not being cached at the CDN. It is a GET request to a URL that does not change, which is exactly what a CDN caches by default. The symptom is identical to a leaked page, and the page will look innocent because the placeholders are there.

## Change country mid-session, then walk to the cart

The tests so far cover a visitor who arrives from one country and stays there. Two transitions break differently.
First, a country change within one session. In one profile, browse a product, then change the country — at the checkout address fields, or through whatever switcher the shop provides — and go back to the product page. WooCommerce now has an address on the session and will tax the cart accordingly. Whether the product page follows depends on whether the cache was told that this visitor is now someone else. If the page still shows the old country’s price, the visitor has one price on the page and another in the cart, and it is the cache, not the tax engine, that is lying.
Second, the walk from browsing to the cart. Add the product from the cached page, open the cart, proceed to checkout. Every step after the first is uncached and computed against the real location. Compare the line-item price at each step with the price the product page showed. Any change between the product page and the cart is a leak that the earlier tests missed — most often because the product page was served before the session existed and the cart was computed after it did.
WooCommerce ships its own partial answer to this, the “Geolocate (with page caching support)” option under default customer location, which redirects the visitor to the same URL with a hash of their location as a query string so that each country gets its own cache entry. It works if every layer honours query strings in the cache key and nobody has configured the CDN to strip them — which is another thing this test reveals.

## Keep the load test separate

Once the prices are right, load-test — but as a separate exercise, with the host’s permission, and against a representative mix. Fetching the home page ten thousand times measures the CDN, not the shop: after the first request every one of them is a HIT and never reaches PHP. A real day on an EU shop is a mix of countries, which under a per-country cache means a lower hit rate than a single-country test will ever show, plus carts and checkouts, which are never cached at all. That is the traffic that has to fit on the server.
The two tests answer different questions and are easy to conflate because both come back with a number. One is “is the number right,” the other is “how fast does it arrive.” A fast wrong price fails the first and passes the second, and it is the first that costs you a customer.

## The checklist

- Two browser profiles, country signal delivered the way production delivers it, cache configured as in production.
- Product URL: warm in A, open in B, compare each to the tax rule. Purge, reverse, repeat.
- Read the cache status at every layer — CDN, server, plugin — on every request.
- Same sequence for the home page, the shop page, one category, search, and the related-products block.
- If prices are fetched by JavaScript, confirm the endpoint is not cached at the CDN.
- Change the country in one session and revisit the product page.
- Add to cart from a cached page and compare the price at every step to checkout.
- Load test afterwards, separately, with a browsing-and-cart mix.
Building a WooCommerce store for the EU? WebKoding makes self-hosted WooCommerce plugins — including SplitVAT for products that carry more than one VAT rate — bought once, no subscriptions. Browse the plugins →

AÖ
Arif Özelci Builds the WebKoding plugins. Answers support himself.
Share on X LinkedIn Copy link
In this article What you are actually looking for The setup: two profiles, not two tabs The sequence, and why the order matters Read the cache status at every layer, not just the last one Not only the product page Change country mid-session, then walk to the cart Keep the load test separate The checklist
Related Caching a WooCommerce shop when the price depends on the customer’s country A product configurator is a pricing problem, not a UI problem
