Geo Targeted Caching extension for W3 Total Cache

The Geo Targeted Caching extension for W3 Total Cache allows you to cache pages on a per country basis, useful when you have a site that displays different content depending on the user’s location. It requires a server (Nginx or Apache) with the GeoIP extension installed or PHP with the GeoIP extension installed.

You can download the latest version here:

Install

Version W3TC 0.9.5 significantly changed how extensions work (plus also how the core works, making this extension potentially not very useful). So there are separate instructions for installing the version of the extension designed to work with W3TC 0.9.5 and later, and the older version of the plugin designed to work with W3TC 0.9.4.1 and earlier. The server config and extension settings are the same for both.

Installing with W3TC 0.9.5 and later

Install as a standard WordPress plugin, then activate the plugin. This doesn’t do anything other than allowing the extension to register itself with W3TC.

Installing with W3TC 0.9.4.1 and earlier

Unzip to the wp-content/plugins/w3-total-cache/extensions/ folder.

Both versions

Then find the extensions page of the W3TC settings in the WordPress admin panel, and activate the extension. Note that the Page cache method on the W3TC General Settings page needs to be set to Disk: Enhanced for the extension to work.

The extension works by modifying the page key used to save the cache file, so that the key includes the country code. If your server has the GeoIP extension installed, then you need to modify the W3TC rules in your server settings, so that the server looks for the cached page with the country code as part of the file name (see below). If you’re using the PHP GeoIP extension, then you don’t need the W3TC rules relating to the page cache in your server config, since every request will have to hit PHP for the country code (and so what cached page to serve up).

Config for Nginx

For Nginx the default W3TC config lines that refer to the cache files look like this (note the exact structure will depend on your permalink structure and W3TC settings):

	if (!-f "$document_root/wp-content/cache/page_enhanced/$http_host/$request_uri/_index.html$w3tc_enc") {
	  set $w3tc_rewrite 0;
	}
	if ($w3tc_rewrite = 1) {
	    rewrite .* "/wp-content/cache/page_enhanced/$http_host/$request_uri/_index.html$w3tc_enc" last;
	}

So, we need to add in the country code, preceded by an underscore, as follows:

	if (!-f "$document_root/wp-content/cache/page_enhanced/$http_host/$request_uri/_index_$geoip_country_code.html$w3tc_enc") {
	  set $w3tc_rewrite 0;
	}
	if ($w3tc_rewrite = 1) {
	    rewrite .* "/wp-content/cache/page_enhanced/$http_host/$request_uri/_index_$geoip_country_code.html$w3tc_enc" last;
	}

Config for Apache

For Apache, on the W3TC General Settings page find the Miscellaneous section. Uncheck the Verify rewrite rules option and save settings. If you don’t do this, then W3TC will overwrite your .htaccess with the default rules, which won’t work for serving geo-localised content. In your .htaccess file change the W3TC Page cache rewrite rule to add in the country code preceded by an underscore.

The default W3TC rewrite rules referring to the page cache files should be something like this (note the exact structure will depend on your permalink structure and W3TC settings):

    RewriteCond "%{DOCUMENT_ROOT}/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}.html%{ENV:W3TC_ENC}" -f
    RewriteRule .* "/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}.html%{ENV:W3TC_ENC}" [L]

With the country code preceded by an underscore added in, they look like this:

    RewriteCond "%{DOCUMENT_ROOT}/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}_%{ENV:GEOIP_COUNTRY_CODE}.html%{ENV:W3TC_ENC}" -f
    RewriteRule .* "/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}_%{ENV:GEOIP_COUNTRY_CODE}.html%{ENV:W3TC_ENC}" [L]

It should be noted that you can also utilise server-side caching without W3TC at all by using the caching mechanisms built into your web server. For example, for Nginx you can use fastcgi cache: Nginx fastcgi_cache with conditional purging for WordPress. However, the W3TC solution does have the benefit that it generates both a plain and gzip version of the file, meaning that Nginx can serve the gzip version rather than having to do the zipping on the fly for each request.

Settings

The extension has two settings, which you can set through the extension’s settings page (accessed via the settings link below the extension entry on the W3TC extensions page). The first setting is a list of country codes that you want pages to be cached for. This allows you to reduce the amount of variations of each page that are cached.

For example, you might be signed up to an affiliate program that only supports the US and Canada, and has different links for each. Your site is configured so that users from Canada see the Canadian link, while those in the US and everyone else sees the US link. There’s no point separately creating a cache file for users from every single country that may visit your site – you only need the page caching for Canada and US / everywhere else. So you’d just enter US,CA as the country codes in the extension settings.

You can write the country codes directly into the setting box (separated by a comma), or you can use the drop down box beside it to select a country to add to the list. The drop down box is just a helper to make it easier if you know the country name you want to add, but don’t know that country’s country code.

The other setting is the default country. This is the country that will be used if the user’s country does not match any of those in the country codes to cache for setting. So, for our example, if you wanted anyone not from the US or Canada to see the pages with the US links, then you’d set this to US.

If using server GeoIP detection, and the content of pages varies for only a specific set of countries, then you should also ideally set up a map as part of your server config (Apache, Nginx) to map the country codes of countries without localised content to the default country code. If you don’t do this, then requests from those countries will hit PHP, and W3TC will serve them the cached file for the default country. Which isn’t as efficient as having the web server serve the cached file directly.

In a future version of the extension I hope to add support for groups. With the current version of the extension, if you serve localised content to the US and Europe (but the content is the same for all countries in Europe), you have to include the country codes for all countries in Europe, and the same page is cached separately for each of those countries. Whereas with groups you could just cache one version of the page for a Europe group, and one version for the US.

For the moment you can achieve the same thing as a group by mapping the countries in the group to a single country code in your server config. e.g. all countries in Europe could be mapped to the country code FR.

A note about use with W3TC 0.9.5 and later

While I have modified the extension to be compatible with W3 Total Cache version 0.9.5, changes were made to W3TC that no longer allow modifying the page cache key early in the process. If using PHP to serve cached pages (i.e. if you don’t have the server rules in place to serve the geo-cached page to the visitor correctly), then you must enable late caching. With late caching enabled most of WordPress is loaded before W3TC tries to fetch the cached page, thus negating some (much?) of the speed benefit of caching.

I intend to do some testing to see if there is any speed benefit available with late caching enabled before doing any further work on this extension.

See also: FAQ and Changelog.

Posted on by xoogu, last updated

20 Responses to “Geo Targeted Caching extension for W3 Total Cache”

  1. ed says:

    thanks, I have used this little plugin code as a basis to solve my advert display caching problem for multiple countries. I modified it to use the cloudflare geoip which is passed in headers for each request as ‘http_cf_ipcountry’, works much the same though, I didnt realise W3 had a way to extend the cache key like that…

    • xoogu says:

      Thanks for that, I didn’t realise Cloudflare could do geoIP lookup for you. That’s pretty neat as a lot of people are on shared servers without geoIP so the only option they have is a PHP based lookup. But if they used Cloudflare then they could avoid having to resort to PHP and use faster server based geo-caching.

      I’ll look at updating my various plugins to make use of that HTTP header.

      Cheers!

      Dave

  2. Alex says:

    Hi Dave,

    Thank you for this great extension. I installed it but since I am on a shared server I cannot use it.

    I am using CloudFlare so what do I have to do in order to benefit from thier GeoIP

    Cheers

    Alex

    • xoogu says:

      Hi Alex

      Unfortunately I’ve got a project ongoing at the moment so I haven’t had time to update the extension with Cloudflare support. However, you should be able to get it working by editing your site’s wp-config.php, and somewhere near the top add: $_SERVER['GEOIP_COUNTRY_CODE'] = $_SERVER['HTTP_CF_IPCOUNTRY'];
      Then the extension should work.

      ($_SERVER['GEOIP_COUNTRY_CODE'] is the variable that contains the country code when your server has GeoIP support enabled. So by setting it to the value of $_SERVER['HTTP_CF_IPCOUNTRY'], which is supplied by Cloudflare, the extension should work exactly the same as if $_SERVER['GEOIP_COUNTRY_CODE'] was populated by the server.)

      Dave

  3. Alex says:

    Hi Dave,

    I added your line

    $_SERVER[‘GEOIP_COUNTRY_CODE’] = $_SERVER[‘HTTP_CF_IPCOUNTRY’];

    just after the W3TC line but I cannot see any effect.

    Please could you help letting me know what I am doing wrong.

    Many thanks

    Alex

  4. dhaval says:

    Hello Author
    when we have to put this extension?
    we have updated the total cache and it contains folder like extension-example but i did not work for me
    basically i want to show the different content to the different countries but due to cache its not working
    Please assist

  5. Alex says:

    Hi dhaval,

    when I used older version of W3TC I could find the extensions folder in this tree:

    wp-content/plugins/w3-total-cache/extensions/

    however, I updated my W3TC and I can no longer see that folder. I tried to create it via FTP and put the zip file there but I still could not see this extension when browsing the W3TC settings.

    It would be good if you could let me know if you managed to resolve this problem.

    Alex

    • xoogu says:

      Hi dhaval and Alex

      The 0.9.5 version of W3TC changed the way extensions work, so all old extensions written for previous versions of W3TC are not compatible with 0.9.5. I’ve re-written the extension now so it will work with 0.9.5, but of course this means the new version is now not compatible with older versions of W3TC.

      You can download the latest version of the extension above, then install and activate it as a standard plugin. After doing that you then need to activate it as a W3TC extension, as before.

      There is also a problem with the 0.9.5 version of W3TC in that they changed how you can modify the page key. So now the page key can only be modified late in the process (after most of WordPress has already loaded). So if you’re relying on W3TC to fetch the cached page (with this extension) then this will greatly slow it down compared to the previous version.

      If you’re only relying on W3TC to generate the cached page, and using server rules to fetch and serve the cached pages, then this change doesn’t make any difference and you should still get a good speed benefit.

      In regards to the Cloudflare support, I haven’t added that to the extension yet, but adding
      $_SERVER['GEOIP_COUNTRY_CODE'] = $_SERVER['HTTP_CF_IPCOUNTRY'];
      near the start of wp-config.php should work (it works for me OK).

      If it’s not working, make sure you’ve cleared the page cache. To help debug, under that line you could add:
      header('CF-IPCountry: '.$_SERVER["HTTP_CF_IPCOUNTRY"]);
      This will let you inspect the HTTP headers (on initial page cache where the request hits PHP) and check what Country code Cloudflare is sending to the server for your page request.

      If you are generating your geo-content using a different method of detecting the user’s geo-location, then this could cause problems. For example, you could be using Cloudflare GeoIP to cache / retrieve pages, but some other GeoIP library to generate the geo sensitive content. In some cases Cloudflare GeoIP could assign one country to the user, whereas the other GeoIP library may assign a different country. So then the page would contain content pertaining to a user from one country, but be cached as the page containing content for users from a different country.

      You need to ensure that all Geo-content is generated using the same method for geo detection.

      If you have Cloudflare caching turned on, it could possibly be the case that they are caching the page for one country and then serving it to visitors from other countries. (I’m not sure how Cloudflare’s caching works).

      In terms of the server rules, you should modify them the same as if you were using a server with GeoIP, except use the Cloudflare GeoIP variable rather than the server GeoIP variable.

      So for Nginx you might have an unmodified W3TC rule like:
      if (!-f "$document_root/wp-content/cache/page_enhanced/$http_host/$request_uri/_index.html$w3tc_enc") {
      set $w3tc_rewrite 0;
      }
      if ($w3tc_rewrite = 1) {
      rewrite .* "/wp-content/cache/page_enhanced/$http_host/$request_uri/_index.html$w3tc_enc" last;
      }

      Which you would then modify to add in the country code to the page key like:
      if (!-f "$document_root/wp-content/cache/page_enhanced/$http_host/$request_uri/_index_$http_cf_ipcountry.html$w3tc_enc") {
      set $w3tc_rewrite 0;
      }
      if ($w3tc_rewrite = 1) {
      rewrite .* "/wp-content/cache/page_enhanced/$http_host/$request_uri/_index_$http_cf_ipcountry.html$w3tc_enc" last;
      }

      For Apache you might have an unmodified W3TC rule like:
      RewriteCond "%{DOCUMENT_ROOT}/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}.html%{ENV:W3TC_ENC}" -f
      RewriteRule .* "/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}.html%{ENV:W3TC_ENC}" [L]

      Which you would modify to add the Cloudflare country code like:
      RewriteCond "%{DOCUMENT_ROOT}/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}_%{HTTP:CF-IPCountry}.html%{ENV:W3TC_ENC}" -f
      RewriteRule .* "/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}_%{HTTP:CF-IPCountry}.html%{ENV:W3TC_ENC}" [L]

      If you’re serving different content to only certain countries, then you should use a rewrite map to ensure that users from countries outside the specific country list are all remapped to the same default country. Then you’d need to use that variable as the country code.

      For example, for a .htaccess solution for Apache (i.e. when you can’t use a rewrite map) you could do something like:
      SetEnvIfNoCase CF-IPCountry "(DE|GB|CA)" GEOIP_COUNTRY_CODE=$1
      SetEnvIf GEOIP_COUNTRY_CODE "^$" GEOIP_COUNTRY_CODE=US

      (Assuming you wanted to serve geo-content to users from Germany, UK, and Canada, and everyone else get geo-content for the USA).

      Then your W3TC rewrite rule would look something like:
      RewriteCond "%{DOCUMENT_ROOT}/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}_%{ENV:GEOIP_COUNTRY_CODE}.html%{ENV:W3TC_ENC}" -f
      RewriteRule .* "/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}_%{ENV:GEOIP_COUNTRY_CODE}.html%{ENV:W3TC_ENC}" [L]

      Hope that makes sense, let me know if not!

      Dave

  6. dhaval says:

    Hello Xoogu,
    Thanks for the response
    we are using apache and frusted with this issue
    can we create the country wise cache only for some pages not for all
    Appreciate your response
    Thanks

  7. John says:

    “While I have modified the extension to be compatible with W3 Total Cache version 0.9.5, changes were made to W3TC that no longer allow modifying the page cache key early in the process. If using PHP to serve cached pages (i.e. if you don’t have the server rules in place to serve the geo-cached page to the visitor correctly), then you must enable late caching.”

    Is this still an issue? Or any way to overcome this without losing out on caching benefits?

    I have switched to WP Super Cache specifically for this so that it works seamlessly with your plugin but would want to use W3 Total Cache instead.

    • xoogu says:

      Hi John

      If you’re using a webserver that includes the GeoIP module, then you should be able to just set the webserver rules up so that it serves any cached pages. That way the request only hits PHP if the page isn’t cached or a no-cache rule is met, e.g. if the request includes a query string or cookie.

      In theory it should be possible to set up server rules when using cloudflare GeoIP detection too, but I’m afraid I’m still really busy at the moment and don’t have time to look into this. When I do get some time, I’ll certainly update with how to do that (assuming it is possible).

      Dave

      • John says:

        “If you’re using a webserver that includes the GeoIP module, then you should be able to just set the webserver rules up so that it serves any cached pages.”

        That’s awesome. However I’m a bit new to this.

        How to set the webserver rules up so that it serves any cached pages? – If you have any tips on that that would be great. 🙂

        So in this case the problem with the W3 Total Cache version 0.9.5 and modifying the page cache key early in the process will be solved?

        So the only thing I don’t know is how to set up server rules in place to serve the geo-cached page to the visitor correctly. If you could hep that would be amazing.

        • xoogu says:

          It depends on what webserver software you’re using, but you just need to modify the default server rules generated by W3TC to add the country code to the bit where it looks for the cached file.

          Instructions for Nginx are here:
          https://www.xoogu.com/geo-targeted-caching-extension-for-w3-total-cache/#nginx

          And instructions for Apache
          https://www.xoogu.com/geo-targeted-caching-extension-for-w3-total-cache/#apache

          • John says:

            Thanks for this, but I’m encountering some problems.

            It’s like this:

            – W3TC without the Geolocation plugin works perfectly.
            – W3TC with the Geolocation plugin makes the site even slower than without any chaching plugin at all.

            I have done the following:

            1. And instructions for Apache
            https://www.xoogu.com/geo-targeted-caching-extension-for-w3-total-cache/#apache

            2. Set up the rules for country specific caching in the W3TC extensions menu.

            Result is that the site is approx. 2x slower than without caching enabled at all.

            Am I doing something wrong?

          • xoogu says:

            Hi John

            If it’s slower than almost certainly the Apache rules aren’t working properly. Sounds like the page is being re-cached and served through PHP for every single request rather than the cached file just being Apache.

            Are you sure your Apache has the GeoIP module installed and working?

            If you look in the WordPress cache folder, can you see the cached page in there generated by W3TC, with the country code as part of the file name?

            Dave

          • John says:

            Hi Dave,

            Thanks for your reply/

            > If it’s slower than almost certainly the Apache rules aren’t working properly. Sounds like the page is being re-cached and served through PHP for every single request rather than the cached file just being Apache.

            Any idea on how to fix this? I’m really clueless at this point.

            > Are you sure your Apache has the GeoIP module installed and working?

            Yes. It’s working perfectly with WP Super Cache and your respective plugin for that.

            > If you look in the WordPress cache folder, can you see the cached page in there generated by W3TC, with the country code as part of the file name?

            Yes.

            It’s just that it’s very slow. Sometimes even slower than without any chaching at all.

            It’s like this:

            No caching: TTFB ~800 ms
            W3TC: ~50 ms
            W3TC with geo 1s-2s

  8. Karbonite says:

    Works great with latest w3tc. Thank you very much, and thanks for your useful answers to the comments as well, great extension and great explanations!

  9. Hello,

    I cant activate the “Geo targeted caching” extension. It says:

    Requirements: Requires your web server has GeoIP functionality available or the PHP GeoIP extension is installed. It does not appear that either are available.

    Server uses Apache so I replaced the W3 code in htaccess with

    RewriteCond “%{DOCUMENT_ROOT}/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}_%{ENV:GEOIP_COUNTRY_CODE}.html%{ENV:W3TC_ENC}” -f
    RewriteRule .* “/wp-content/cache/page_enhanced/%{HTTP_HOST}/%{REQUEST_URI}/_index%{ENV:W3TC_PREVIEW}_%{ENV:GEOIP_COUNTRY_CODE}.html%{ENV:W3TC_ENC}” [L]

    I also have “Net_GeoIP extension” enabled on the server.

    What am I missing?

    Thanks,
    Katey

Leave a Reply to xoogu