WordPress currency switcher GeoIP not working: causes and fixes
When the WordPress currency switcher GeoIP rules are not working, the symptoms vary: every visitor gets the same currency, visitors from one country keep seeing the welcome currency, or the test shortcode shows an empty country. In most cases the plugin looks up exactly the address it is given. The real cause is the IP address the server hands to PHP, a page cache that serves one visitor's page to everyone, or a test that was not a first visit. Below is how detection works, how to find out in a few minutes which of these you have, and the fix for each.
How GeoIP chooses the currency
In the GeoIP rules tab of Settings → Currency Switcher, each currency has a list of countries. On a visitor's first visit the plugin finds the country of the visitor's IP address and, if a currency lists that country, switches to that currency. Countries that are in no list get the Welcome currency from the Options tab. After that first visit the plugin remembers the currency in the Currency storage and does not apply the rules again, so a currency the visitor picked by hand stays. The full setup is described in set the currency by visitor country; this page is about finding what breaks it.
Two details explain most of the problems below:
- Which address is used. The plugin reads the
X-Real-IPrequest header first, then the first address inX-Forwarded-For, thenREMOTE_ADDR, the address of the machine connected to the web server. - Which database is used. The Geo position option offers Open Geo IP, the database bundled with the plugin (
lib/GeoIP.dat), and GeoIP2 (MaxMind API key is required), MaxMind's GeoLite2 database downloaded to your site with a free key from MaxMind. With Open Geo IP, when a request carries Cloudflare'sCF-IPCountryheader, the plugin takes the country code from that header instead of its own lookup.
Diagnose first: a test page and a real first visit
Create a page with these two shortcodes and exclude its URL from every page cache (cache plugin, server cache, Cloudflare):
[wpcs_check_country] [wpcs_price value=10]
[wpcs_check_country] prints Your current country is: Germany, Code: DE. It runs a fresh lookup on every page view and ignores the remembered currency, so it answers only one question: is the country right? The [wpcs_price] amount shows which currency is active for you. Without the cache exclusion the country line itself gets cached and shows the country of whoever loaded the page first.
To check the currency as well, every test must be a first visit, and what counts as a new visitor depends on Currency storage:
| Currency storage | The visitor is remembered by | A real first visit for testing |
|---|---|---|
| transient (default) | IP address, for one day | A different connection (VPN, mobile data). A private window on the same connection is not new. |
| cookie | The wpcs_guest_id cookie, for one day | A new private window. Switching a VPN on in the same window is not new. |
| session | The PHP session cookie | A new private window. Switching a VPN on in the same window is not new. |
Toggling a VPN in a browser that has already visited the site is the most common false alarm: the country line changes, the currency does not, and that is the intended behavior. A web service that loads your page from several countries gives the most realistic result. Now compare what the test page shows with this table:
| What the test page shows | Cause |
|---|---|
| The same country for every test, often the country of your server or CDN | The server passes one IP for all visitors |
| United States for every test | PHP sees the loopback address of a local proxy |
| The right country, but the price stays in the welcome currency | Not a first visit, a cached page, or no rule for that country |
| Empty name and code | No usable address, or an IPv6 visitor with Open Geo IP |
| DB is empty. Please download the database. with code error | GeoIP2 is selected but its database was never downloaded |
| Europe or Asia/Pacific Region | The bundled database knows only the region for that address |
Everyone gets the same country: fix IP forwarding
When a CDN, reverse proxy, load balancer or cache server (Cloudflare, Nginx in front of Apache, Varnish, a hosting proxy) sits in front of WordPress, the connection PHP sees comes from that machine, not from the visitor. Unless the server is told to restore the original address, every visitor gets the proxy's address, and therefore its country. A common variant: Nginx is set to send X-Real-IP with its own $remote_addr, which behind Cloudflare is the Cloudflare server's address. The plugin reads that header first, so everyone is detected in the country of the Cloudflare data center.
The fix is on the server. With Nginx, the real IP module replaces the proxy address with the visitor's address before PHP sees it:
# nginx.conf, inside the http {} or server {} block.
# Add one set_real_ip_from line for every range listed at https://www.cloudflare.com/ips/
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 2400:cb00::/32;
real_ip_header CF-Connecting-IP;
On Apache, mod_remoteip does the same with RemoteIPHeader CF-Connecting-IP and a RemoteIPTrustedProxy line for the Cloudflare ranges. Cloudflare's guide Restoring original visitor IPs has both configurations. Without Cloudflare the header is usually X-Forwarded-For, and the trusted address is that of your own proxy. On managed hosting you cannot edit these files: ask the host to make the real visitor IP available to PHP in REMOTE_ADDR or X-Real-IP. Reload the test page from two different networks; it should show two different countries.
Cloudflare: use its country header
If the site is behind Cloudflare, turn on IP Geolocation in the Network section of the Cloudflare dashboard (see Cloudflare IP Geolocation). Cloudflare then adds a CF-IPCountry header to every request, and with Geo position set to Open Geo IP the plugin uses it automatically. On the test page the code then comes from Cloudflare while the country name still comes from the IP lookup; if the two disagree, the code is what the rules use. With GeoIP2 the header is not read, but you can pass it to the rules with the wpcs_geo_position filter:
/*
* WPCS: take the visitor country from Cloudflare's CF-IPCountry header.
* Child theme functions.php or a code snippets plugin.
*/
add_filter('wpcs_geo_position', function ($country) {
if (!empty($_SERVER['HTTP_CF_IPCOUNTRY'])) {
$cf = strtoupper(sanitize_text_field(wp_unslash($_SERVER['HTTP_CF_IPCOUNTRY'])));
// two-letter country codes only; XX means unknown
if (preg_match('/^[A-Z]{2}$/', $cf) && $cf !== 'XX') {
return $cf;
}
}
return $country;
});
The filter changes the country used for choosing the currency. [wpcs_check_country] still prints the plugin's own lookup, so test the result with the price shortcode and a first visit.
Every visitor is detected in the United States
When Nginx or Varnish runs on the same machine and hands requests to Apache, PHP sees the server's own loopback address. The plugin treats that address as a development copy running on a computer and replaces it with a fixed public test address located in the United States, without reading any header. Apache must restore the visitor's address with mod_remoteip:
# Apache 2.4 configuration, mod_remoteip enabled.
# Use the header your front proxy sends (X-Real-IP or X-Forwarded-For).
RemoteIPHeader X-Real-IP
RemoteIPInternalProxy 127.0.0.0/8
Side effect: one remembered currency for everyone
The default transient storage keys its memory by IP address. While all visitors arrive with one address, they share one memory: the first visitor of the day decides the currency for everybody, and a visitor who switches currency switches it for everyone else too. Fixing the forwarding solves this. Setting Currency storage to cookie also helps, because it remembers each browser separately, whatever its IP.
The country is right, but the currency does not change
- Not a first visit. Test again following the storage table above.
- A cached page. A page cache serves stored HTML, and the GeoIP step never runs for the next visitor. In the Options tab set I am using cache plugin on my site to Yes. Prices printed by
[wpcs_price]are then redrawn after the page loads by an AJAX request (wpcs_get_prices_html) that runs for the current visitor, GeoIP rules included, and the switcher is set to the returned currency. Clear every cache layer after changing the option; if prices still do not update, see currency switcher not working with a cache plugin. - No rule for that country. The rules contain countries only; there is no Europe entry, so add each country to its currency. A country listed under two currencies goes to the first one.
- Fixed prices.
[wpcs_price type="fixed" value="USD:15,GBP:45"]shows none for a currency missing fromvalue. Typographic quotes (”) pasted from a word processor also break the attributes; type straight quotes. - Wrong amounts after switching. The base currency in the Currencies tab must have the rate 1; conversions are calculated from it.
Empty or wrong country for some visitors
- Empty for some visitors. The Open Geo IP lookup handles IPv4 addresses only. Visitors who reach the server over IPv6, common on mobile networks, get no country. GeoIP2 handles both. Empty for everyone means the address PHP receives is missing or belongs to a private network (a container or load balancer): fix the forwarding as above.
- Europe or Asia/Pacific Region. No country rule can match these. Switch to GeoIP2.
- Wrong country. Address ranges change hands, and plugin updates bring a newer copy of the bundled database, so update the plugin first. For better accuracy select GeoIP2 (MaxMind API key is required) in Geo position, paste a free key from your MaxMind GeoLite2 account into MaxMind license key and click Save options. The Update/Download DB button appears only after the key is saved; click it and wait for Database downloaded successfully. The file is stored as
wp-content/uploads/currency-switcher/GeoLite2-Country.mmdband is not refreshed automatically, so click the button again every few months. - GeoIP2 worse than Open Geo IP. The database file is missing or old. Download it again, or switch back to Open Geo IP.
- VPN tests. A VPN exit server can be registered in a different country than the one the VPN app displays. Compare with a second test source before changing settings.
If the server cannot pass the visitor's address and the site is not behind Cloudflare, GeoIP cannot work. Then remove the rules, set the Welcome currency to the currency most of your visitors use, and let them choose with the [wpcs] switcher.
Checklist
- The test page is excluded from all caches and shows different, correct countries from two networks.
- The server passes the real visitor IP, or Cloudflare IP Geolocation is on with Open Geo IP.
- With GeoIP2: the key is saved and the database was downloaded.
- Each test was a real first visit for the selected Currency storage.
- I am using cache plugin on my site is Yes on a cached site, and all caches were cleared.
- Each country is listed under its currency, and the base currency rate is 1.
If all of this is true and visitors still get the wrong currency, open a topic on the WPCS support forum with the link to your test page, your Geo position and Currency storage settings and the cache or CDN you use.