WooCommerce Currency Switcher Behind Cloudflare: APO, Cache and GeoIP
A WooCommerce currency switcher behind Cloudflare depends on two things: which pages Cloudflare caches and hands out as finished HTML, and what it tells WordPress about the visitor. When either is wrong the symptoms look alike: everyone gets the same currency, the switcher seems to do nothing, Cloudflare APO stops caching the shop, or GeoIP rules pick the wrong country. This is the Cloudflare-specific setup for FOX; the general conflict between prices and page caches is explained in why a currency switcher and a page cache fight each other.
What Cloudflare does with your shop pages
Start by finding out which of three setups you have, because the fixes differ.
| Setup | HTML pages cached at Cloudflare? | What matters for FOX |
|---|---|---|
| Proxy only (orange cloud, default caching) | No. Cloudflare does not cache HTML by default, only static files such as CSS, JavaScript and images. | The visitor's IP address and country reaching WordPress. A cache plugin on the server is a separate question. |
| Cache Rules that make HTML eligible for cache | Yes, one copy per URL for everyone. | FOX cache mode, a storage without cookies, and the right exclusions. |
| APO (Automatic Platform Optimization for WordPress) | Yes, at the edge. | The same as Cache Rules, plus APO's own rules for cookies and query strings. |
To see what happens to a page, open a category page twice in a private window, with the Network tab of the developer tools open, and read the response headers of the page request itself:
cf-cache-status: HITmeans the page came from Cloudflare's cache;MISS,EXPIREDorBYPASSmean it was built by WordPress;DYNAMICmeans Cloudflare does not consider it cacheable at all.- With APO,
cf-apo-via: cachemeans the page came from the APO cache; any value starting withoriginmeans APO fetched it from your server. - A
set-cookieheader in the page response is the usual reason why a page that should be cached never is.
From a terminal, curl -s -o /dev/null -D - https://example.com/product-category/shoes/ prints the same headers of a normal GET request; run it twice.
Currency switcher settings for Cloudflare APO and cached HTML
When Cloudflare caches HTML, the same copy of a page reaches everyone, so FOX has to fix the prices in the browser and must not put anything personal into the page response. In the FOX settings (WooCommerce → Settings → Currency), Options tab:
- Currency storage = Transient. FOX Session sets a session cookie (
wp_woocs_session_…) on a visitor's first response, and PHP Session sets one too, while PHP's session handling also addsCache-Control: no-store, no-cacheheaders. Cloudflare does not store a response that sets a cookie or forbids caching, so with either session storage the pages keep coming from your server and the APO hit rate collapses:cf-apo-via: origin,resnokin the headers. Transient sets no cookie. Renaming the session cookie with thewoocs_cookiefilter does not change this: the first response still carries aSet-Cookieheader. - I am using cache plugin on my site = Yes. FOX then redraws prices in the visitor's currency after the cached page loads. Together with Transient it also turns on the browser key mode (since FOX 2.5.0): the browser keeps a random key in localStorage and sends it with jQuery AJAX requests (the
woocs_skparameter) and with same-originfetch()requests such as the block checkout and PayPal buttons (theX-WOOCS-SKheader). The key never appears in a page URL, so it does not split the cache per visitor. Keep FOX up to date: the first builds of this mode sent the key only with jQuery requests, so PayPal orders created throughfetch()came out in the base currency and failed withCANNOT_MIX_CURRENCIES. - No GET data in link = No. In browser key mode a switch first saves the currency for the browser key, then opens the page with
?currency=in the address. That request goes to your server (APO never caches it, see below), which renders the page in the new currency and also stores it under the visitor's IP address, the key FOX uses for pages rendered without AJAX, such as a classic cart page.
Prices preloader makes little difference here: in FOX 2.5.5 its loading animation is removed as soon as the redraw request is sent, not when the answer arrives, so it does not hide the short moment in which the cached prices are visible.
APO adds rules of its own. It bypasses the cache for visitors who carry cookies starting with wp-, wordpress, woocommerce_ and a few other prefixes, so logged-in users and visitors with something in the cart get fresh pages anyway. It also bypasses the cache for any URL with a query parameter outside its short allow-list of tracking parameters. Two consequences for a currency shop:
- Links with
?currency=EURalways reach your server, which is what the currency switch needs. - WooCommerce's Default customer location = Geolocate (with page caching support) adds a
?v=parameter to shop URLs, and APO serves none of those URLs from its cache. FOX does not need this setting: it asks WooCommerce for the country directly, whatever Default customer location says, and on a cached page it does so in the redraw request (with one gap, closed by the snippet further down). With APO, choose Geolocate or Shop base address according to how you want WooCommerce to estimate taxes and shipping.
Cache Rules: what to exclude
With Cache Rules you decide yourself what Cloudflare stores, and a rule that is too broad causes the worst currency problems: a cached cart or checkout shows one visitor's totals to another. Keep these out of the cache with a rule set to Bypass cache, or exclude them from your caching rule:
- the cart, checkout and my-account pages (use your own page slugs) and
/wp-admin/; /wp-json/and requests withwc-ajax=in the query string;- URLs whose query string contains
currency=, so that a currency switch is always handled by WordPress.
For the rule that caches shop pages, set Edge TTL to one of the options that use the Cache-Control header if present. WooCommerce sends no-cache headers on the pages set as cart, checkout and my-account page in its settings (a checkout built on another page by a funnel plugin gets none), and with Ignore cache-control header and use this TTL Cloudflare would cache them anyway if your rule matches them by mistake. FOX's own requests to admin-ajax.php are POST requests, which Cloudflare does not cache.
Wrong country or one currency for everyone
FOX has no geolocation database of its own. It calls WC_Geolocation::geolocate_ip(), applies its GeoIP rules to the country WooCommerce returns, and does this once per visitor. When WordPress renders a page, that happens in PHP; on a page served from Cloudflare's cache it can only happen in the redraw request, a POST to admin-ajax.php that always reaches your server. So geolocation behind Cloudflare needs three things: the right country on every request, the right visitor address, and a redraw that applies the rules. To see what WooCommerce detects, put [woocs_geo_hello] on an uncached test page; the full procedure is in WooCommerce geolocation shows the wrong currency.
Let Cloudflare send the country
Turn on IP Geolocation in the Network settings of your Cloudflare zone, or the Add visitor location headers Managed Transform, which includes it. Cloudflare then adds a CF-IPCountry header to every request, and WooCommerce reads it (as HTTP_CF_IPCOUNTRY, among a few server country variables) before it tries any IP lookup. No MaxMind key and no code are needed for FOX's GeoIP rules then. Old advice to feed Cloudflare's country in through the woocommerce_geolocate_ip filter is unnecessary today; keep that filter for country sources WooCommerce does not know. Two special values exist: XX for addresses without country data and T1 for the Tor network. Neither matches a GeoIP rule, so those visitors get the Welcome currency.
Restore the visitor's IP address
WordPress still needs the real visitor address: FOX keys Transient storage by IP for pages rendered without AJAX (and for everything when the cache option is off), and WooCommerce falls back to the IP when no country header arrives. Behind Cloudflare the connection comes from a Cloudflare address; the visitor's address travels in the CF-Connecting-IP and X-Forwarded-For headers.
- FOX reads
CF-Connecting-IPitself, so its storage sees the visitor even when nothing else is configured. - WooCommerce reads
X-Real-IPfirst, thenX-Forwarded-For, then the connection address. A common failure is an nginx proxy in front of Apache that passesX-Real-IPwith the address of the connection, which behind Cloudflare is a Cloudflare server. Without the country header, WooCommerce then looks up Cloudflare's address instead of the visitor's, and most visitors get the same country, often the United States. - The fix is at server level: nginx's real IP module with
set_real_ip_fromfor every Cloudflare range andreal_ip_header CF-Connecting-IP;, or Apache'smod_remoteipwithRemoteIPHeader CF-Connecting-IPandRemoteIPTrustedProxylines. Cloudflare publishes its ranges and the full instructions in restoring original visitor IPs; on managed hosting, ask the host to set it up. - Leave the Remove visitor IP headers Managed Transform off. It removes
CF-Connecting-IP, and FOX then falls back to the same headers as WooCommerce: on a server like the one just described, visitors who come through the same Cloudflare address share one stored currency on server-rendered pages.
GeoIP rules on cached pages
In FOX 2.5.5 the redraw request first compares the cached copy's currency with the visitor's current currency, and when they are equal it answers "nothing to change" without reaching the GeoIP step. A new visitor starts in the Welcome currency, so on every page cached in the Welcome currency the GeoIP rules are skipped: with a rule Germany → EUR, a German visitor keeps the Welcome currency and gets euros only on the first page that happens to be cached in another currency. That is the "GeoIP works on some pages only" pattern behind a full-page cache. The snippet below runs the GeoIP step before the comparison, once per visitor, and lets a currency picked in the switcher win:
<?php
/**
* FOX behind a full-page cache (Cloudflare APO or Cache Rules):
* apply the GeoIP rules in a new visitor's first price redraw,
* also when the cached copy is already in the Welcome currency.
* Add to the child theme's functions.php or a code snippets plugin.
*/
function my_fox_geoip_in_redraw() {
global $WOOCS;
if ( ! is_object( $WOOCS ) || empty( $WOOCS->storage ) ) {
return;
}
// Once per visitor: FOX sets this flag when it has applied the GeoIP rules.
if ( ! intval( $WOOCS->storage->get_val( 'woocs_first_unique_geoip' ) ) ) {
$WOOCS->init_geo_currency();
}
}
function my_fox_geoip_after_manual_choice() {
global $WOOCS;
// A currency chosen in the switcher before the first redraw must win.
if ( is_object( $WOOCS ) && ! empty( $WOOCS->storage ) ) {
$WOOCS->storage->set_val( 'woocs_first_unique_geoip', 1 );
}
}
foreach ( array( 'woocs_get_products_price_html', 'woocs_get_variation_products_price_html', 'woocs_get_custom_price_html' ) as $my_fox_action ) {
add_action( 'wp_ajax_' . $my_fox_action, 'my_fox_geoip_in_redraw', 1 );
add_action( 'wp_ajax_nopriv_' . $my_fox_action, 'my_fox_geoip_in_redraw', 1 );
}
add_action( 'wp_ajax_woocs_set_currency_ajax', 'my_fox_geoip_after_manual_choice', 1 );
add_action( 'wp_ajax_nopriv_woocs_set_currency_ajax', 'my_fox_geoip_after_manual_choice', 1 );
Skip the opening <?php line if your file already has one. FOX's own redraw handlers run at the default priority 10, so this code runs first, and the comparison then sees the currency of the visitor's GeoIP rule. The redraw runs only on pages with product prices, so a visitor who lands on a blog post gets the GeoIP currency on the first shop page. To test it, purge the Cloudflare cache, load a category page from a country without a GeoIP rule until it is cached (a HIT) in the Welcome currency, then open it through a VPN from a country with a rule in a browser profile that has never visited the shop: the prices must change to that country's currency. With the settings above the browser key, not the IP address, decides who is a new visitor.
Purge and test after every change
A cached page keeps the prices it was built with, and FOX's redraw replaces them only when the visitor's currency differs from the cached copy's. After you change currencies, rates, decimals or symbols in FOX, visitors whose currency matches a cached copy see the old amounts until that copy expires. The Cloudflare WordPress plugin purges APO pages when you edit content, not when FOX settings or rates change, so use Purge Everything in Cloudflare's caching configuration, and purge the cache plugin on your server, after such changes. With automatic rate updates and Cache Rules, an Edge TTL no longer than the update interval keeps converted prices current; with APO, purge after large rate moves.
Then test in this order:
- Run the curl command above twice for a category page: the second answer shows
cf-cache-status: HIT(andcf-apo-via: cachewith APO) and noset-cookieheader. - Open the page in two browsers that have never visited the shop. Switch the currency in one: it changes there, and the other keeps its own currency after a reload.
- Add a product to the cart in the second currency and go to checkout: the totals stay in that currency.
- If something fails, take Cloudflare's cache out of the test: switch APO or your Cache Rule off for a moment, or turn on Development Mode in the caching configuration, which suspends edge caching for three hours, then purge and repeat. If the problem stays, the cause is on your server, such as a cache plugin, a server cache or a JavaScript optimizer, not Cloudflare. Cloudflare's own script optimization, Rocket Loader, delays scripts like those optimizers do; if prices keep spinning, test with it off and see currency switcher stuck loading after JS optimization.
Checklist
- You know whether Cloudflare caches your HTML (proxy only, Cache Rules or APO), from the response headers.
- With cached HTML: Currency storage = Transient, I am using cache plugin on my site = Yes, No GET data in link = No, FOX up to date.
- Cart, checkout, account,
/wp-json/,wc-ajaxandcurrency=URLs never cached; Edge TTL respectsCache-Control. - With APO: Default customer location not set to the page-caching variant.
- IP Geolocation on; real visitor IP restored on the server; Remove visitor IP headers off.
- With GeoIP rules and cached pages: the redraw snippet in place.
- Cloudflare and server caches purged after changing FOX settings or rates.
If the currency is still wrong with all of this in place, open a topic on the FOX support forum with a page link, the response headers of that page and your FOX storage and cache settings, and we will look at it.