Redirect traffic geographically with NGINX & geolocation
Sometimes you care about the region or country users come from to present them with a different experience, perhaps due to language or currency for a shop. Let's explore how to do that in a relatively easy few steps.
The setup
You've got some form of linux or bsd running nginx - let's assume it's Debian or Ubuntu.
First Steps
Install the geo-ip tools and update the database.
sudo apt-get update
sudo apt-get install nginx-full geoip-database
curl http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz | gunzip > /usr/share/GeoIP/GeoLiteCity.dat
The information is available as many formats: country or even city, however those are respectively long and impossible to list here. Also bare in mind the ip-based geolocation is an approximation that is rough but mostly adequate, you can usually rely on the country information if a user isn't using a proxy, the state and city information can vary especially with mobile networks. Let's just stick to continents here - below are the ones defined in the specification.
Code | Continent | Code | Continent |
---|---|---|---|
AF | Africa | AN | Antarctica |
AS | Asia | EU | Europe |
AS | North america | OC | Oceania |
SA | South america |
Enter nginx
First of all, edit your nginx.conf
in /etc/nginx/nginx/ or whereever you keep it, and add the following geoip_city
definition as per below under http
section.
http {
# Tell Nginx where we keep our GeoLocation database
geoip_city /usr/share/GeoIP/GeoLiteCity.dat;
# all the rest...
}
Using a very clever nginx directive called map
which you can read all about here, we define a variable essentially based on a lookup table (which in this case uses the $geoip_city_continent_code to map the resultant value into $closest_server).
map $geoip_city_continent_code $closest_server {
default international-waters.mybrand.com;
AF africas.mybrand.com;
AN penguins.mybrand.com;
AS asia.mybrand.com;
EU europe.mybrand.com;
NA northamericas.mybrand.com;
OC oceania.mybrand.com;
SA southamericas.mybrand.com;
}
Or to see this visually:
Now that'll give us a value we can use, but what do we do with it? Well there's a few options you could 301/302 (debatable which is more true as it hasn't been redirected either temporarily or permanently but anyway...), or you can use a rewrite rule to internally rewrite the url.
server {
listen 127.0.0.1:80;
server_name shop.mybrand.com;
rewrite ^ $scheme://$closest_server$request_uri break;
}
server {
listen 127.0.0.1:80;
server_name africas.mybrand.com penguins.mybrand.com asia.mybrand.com europe.mybrand.com northamericas.mybrand.com oceania.mybrand.com southamericas.mybrand.com;
root /srv/shop.mybrand.com/html;
index index.html;
}
Conclusion
It's actually surprisingly simple to do this, and takes the heavy-lifting out of your code, which is a huge benefit for portability, and allows a much more flexible setup. This is just the tip of the iceberg as their are other parameters available such as country, state and region (as discussed). A word to the wise, if you're wanting to do this for language reasons, try using the user's locale (it'll be much more accurate and specific).