Cache WooCommerce Currency from GeoIP with Varnish 4 vmod

Aelia Currency Switcher allows WooCommerce and Easy Digital Downloads online business owners to display their products in different currencies depending on the visitor’s geolocation. It works by probing each visitor’s IP address and setting the currency based on their country, city or other parameters the business owner has chosen to decide the currency.

I had a long discussion with Diego on the Advanced WooCommerce facebook group about the Aelia plugin and how to cache the specific currencies separately more powerfully. Diego already had done what he could to speed things up by creating addons for some caching plugins. aelia Currency Switcher for WooCommerce probes every single visitor for their geolocation, after that the currency cookies are used as a cache key. This is very clever, but can still result in a first slow page load for each visitor and first speed impressions matter! I told Diego that this system could be improved with the Varnish MaxMind GeoIP vmod.

I suggested to Diego it would be possible to only probe the first visitor from each continent, country or city once, through the Varnish caching layer. Such operation would allow us to serve cached version of the site content to all subsequent visitors from the same region, without requiring the aelia plugins to perform the detection.

You can extend Varnish functionality with vmods. In this guide we are going to use the maxminddb vmod to cache the different currency values with Varnish. You must have root access to your VPS or dedicated server running Varnish or be able to modify your Varnish configuration. I used Ubuntu 16.04 and Debian 8 for this guide.

You must already have Varnish configured with WooCommerce.

WooCommerce Currency Switcher Speed Tests

For this test I set up a Digital Ocean VPS with nginx, PHP7, MariaDB and Varnish 4 with WooCommerce and added 10 products with the Product Generator.

I installed the Aelia WooCommerce Currency Switcher plugin and set 3 different currencies: EUR, USD and GBP.

I used the Varnish configuration outlined below and ran the tests with and without cache.

WooCommerce Currency Speed without Varnish Cache

Page load time from Sweden without Varnish was 882 ms

WooCommerce Currency Speed with Varnish Cache

Page load time from Sweden with Varnish was 279 ms

If you want a 300% WooCommerce speed improvement for your different currencies, read on!

Cache WooCommerce Currency from GeoIP with Varnish 4 vmod

Installation overview

  • Install MaxMind GeoIP libraries
  • Install the Varnish MaxMind GeoIP vmod
  • Download the MaxMind Lite GeoIP databases
  • Configure Varnish vcl to use the GeoIP vmod
  • Test Varnish is caching currency specific pages based on GeoIP

Configure Aelia WooCommerce Currency Switcher

Make sure you go to WooCommerce Currency Switcher > Geolocation tab > check Enable automatic selection of Currency depending on Visitors’ location

Set any custom currency information with Aelia’s guide, I used Pluginception to create the custom plugin.

Install the MaxMind Libraries

These packages are required for the Varnish vmod to compile. For Debian 9 Stretch and Ubuntu 16.04 Xenial and later use these.

sudo apt-get update
sudo apt-get install build-essential libtool libvarnishapi-dev pkg-config python-docutils libmaxminddb-dev libmaxminddb0 -y

For Debian Jessie or Ubuntu 14.04 use these commands to install the Varnish libraries and MaxMind libraries from the .deb packages in Debian Stretch.

There may be updated packages, you can get the updated links from here.

sudo apt-get update
sudo apt-get install build-essential libtool libvarnishapi-dev pkg-config python-docutils -y
cd /tmp
wget https://ftp.de.debian.org/debian/pool/main/libm/libmaxminddb/libmaxminddb-dev_1.2.0-1_amd64.deb
sudo dpkg -i libmaxminddb-dev*.deb
wget https://ftp.de.debian.org/debian/pool/main/libm/libmaxminddb/libmaxminddb0_1.2.0-1_amd64.deb
sudo dpkg -i libmaxminddb0*.deb
rm libmasminddb*

Install the GeoIP Varnish vmod

The madminddb vmod lets you use the MaxMind GeoIP databases with Varnish.

cd /tmp
git clone https://github.com/simonvik/libvmod_maxminddb
cd libvmod_maxminddb
./autogen.sh
./configure VMOD_DIR=/usr/lib/varnish/vmods/
sudo make
sudo make install
libtool --finish /usr/lib/varnish/vmods/

Now the maxminddb vmod has been installed so Varnish can use the vmod to communicate with the MaxMind databases but we still need the GeoIP databases!

Download the MaxMind GeoIP Databases

The free MaxMind databases called GeoLite2 can be downloaded from here

The professional MaxMind databases are more precise but have a monthly subscription fee.

To use the MaxMind GeoLite2 databases use these commands

cd /etc/varnish
sudo wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz
sudo wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz
sudo gunzip GeoLite2*
sudo rm -rf GeoLite2*.gz

Configure Varnish to Detect GeoIP

These are the snippets we are going to include in the existing Varnish vcl for WooCommerce

  • sub vcl_init – load the geoip databases
  • sub vcl_recv – set headers based on geoip
  • sub vcl_hash – specify cache separation based on calculated currency

Open the Varnish vcl first

sudo nano /etc/varnish/default.vcl

sub vcl_init

First import the maxminddb and std modules.

In sub vcl_init we load the MaxMind databases.

# new 4.0 format.
vcl 4.0;
import maxminddb;
import std;

# Default backend definition. Set this to point to your content server.
backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

#Initialize the GeoIP database locations
sub vcl_init{
   maxminddb.init_db("/etc/varnish/GeoLite2-Country.mmdb");
   maxminddb.init_db("/etc/varnish/GeoLite2-City.mmdb");
}

sub vcl_recv

In the sub vcl_recv section we are going to detect the IP location and set some headers.

Then we will use the headers to set the currency and tell Varnish to cache based on these headers.

We are also explicitly caching the aelia query string and aelia currency cookie.

sub vcl_recv {

#get continent
set req.http.X-continent = maxminddb.query_continent(client.ip);
#get country
set req.http.X-Country = maxminddb.query_country(client.ip);
set req.http.X-state = maxminddb.query_state(client.ip);
set req.http.X-city = maxminddb.query_city(client.ip);
set req.http.X-postalcode = maxminddb.query_postalcode(client.ip);

#set currency based on country header
if (req.http.X-Country == "NL") {
    set req.http.X-Currency = "EUR";
} else if (req.http.X-Country == "US") {
    set req.http.X-Currency = "USD";
}

#set currency more specifically for multiple countries that share same currency
else if (req.http.X-Country ~ "DK|FO") {
    set req.http.X-Currency = "DKK";
}

# for debugging in varnishlog
std.log("Continent value is: " + req.http.X-continent);
std.log("Country value is: " + req.http.X-Country);
std.log("Currency is: " + req.http.X-Currency);
std.log("City: " + req.http.X-city);
std.log("State: " + req.http.X-state);
std.log("Postal: " + req.http.X-postalcode);

#cache aelia currency cookie
if (req.http.cookie ~ "aelia_cs_selected_currency") {
    return(hash);
  }

#cache aelia query strings explicitly
if (req.url ~ "\?aelia_(cs_currency|customer_country|customer_state)=")
    return(hash);
}

sub vcl_hash

In the sub vcl_hash section we are caching specific versions of both the aelia currency cookie and the custom header we set for the currency.

sub vcl_hash {
#store cache based on aelia currency cookie
    if (req.http.cookie ~ "aelia_cs_selected_currency") {
        hash_data(req.http.cookie);
    }

#hash based on currency header Varnish sets
    hash_data(req.http.X-Currency);
}

Ctrl+X, Y and Enter to Save.

Test the Varnish GeoIP Configuration

This command will verify the Varnish vcl syntax is valid

varnishd -C -f /etc/varnish/default.vcl

Now reload the Varnish service

sudo service varnish reload

Using the command line on two different DigitalOcean VPS’s from the same region, we can verify if Varnish is caching our custom currency shop and product pages by using cURL.

sudo apt-get install curl -y

Now cURL the shop URL or a product page. Using the -I switch which returns the response headers.

curl -I https://wp-bullet.online/shop/

At first you will probably see the X-Cache: MISS header since it is the first visit from that specific continent.

HTTP/1.1 200 OK
Date: Sat, 27 Aug 2016 07:52:43 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: __cfduid=dd68b58232cba6c1f53425d33bd2677741472284363; expires=Sun, 27-Aug-17 07:52:43 GMT; path=/; domain=.wp-bullet.online; HttpOnly
Link: <https://wp-bullet.online/wp-json/>; rel="https://api.w.org/"
Vary: Accept-Encoding
X-Varnish: 491537 32774
Age: 33489
Via: 1.1 varnish-v4
X-Cache: MISS
Server: cloudflare-nginx
CF-RAY: 2d8df21881550c05-AMS

Repeat the same cURL command

curl -I https://wp-bullet.online/shop/

Now you will see an X-Cache: HIT showing Varnish has cached the WooCommerce currency page based on the headers we set.

HTTP/1.1 200 OK
Date: Sat, 27 Aug 2016 07:52:53 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Set-Cookie: __cfduid=dd68b58232cba6c1f53425d33bd2677741472284363; expires=Sun, 27-Aug-17 07:52:43 GMT; path=/; domain=.wp-bullet.online; HttpOnly
Link: <https://wp-bullet.online/wp-json/>; rel="https://api.w.org/"
Vary: Accept-Encoding
X-Varnish: 491537 32774
Age: 33491
Via: 1.1 varnish-v4
X-Cache: HIT
Server: cloudflare-nginx
CF-RAY: 2d8df21881550c05-AMS

On another VPS pair in the same continent or country you can run the test again. This should show you an X-Cache HIT immediately.

After verifying one continent works you can move on to the next continent using a VPS from that continent to do cURL tests. I recommend using pingdom tools if you want to see a picture of the shop page and verify it displays the correct currency.

If you need help leave a comment or get in touch, I’m always happy to make WooCommerce faster :).