How to use GeoIP with Nginx on Ubuntu 16.04

This tutorial explains how to use the GeoIP module with nginx on Ubuntu 16.04 to find out where your visitors come from. The GeoIP module sets multiple variables like $geoip_country_name, $geoip_country_code, $geoip_city, etc. that you can use in your PHP scripts or directly in your nginx configuration, for example to serve content in different languages based on the user's country.

 

1 Preliminary Note

I'm using the website www.example.com here with the document root /var/www/www.example.com/web/ and the Nginx vhost configuration file /etc/nginx/sites-enabled/www.example.com.vhost. I will use this tutorial for the basic Ubuntu-Nginx setup. https://www.howtoforge.com/tutorial/installing-nginx-with-php7-fpm-and-mysql-on-ubuntu-16.04-lts-lemp/

This tutorialis compatible with ISPConfig nginx setups as well.

A note for Ubuntu users:

Because we must run all the steps from this tutorial with root privileges, we can either prepend all commands in this tutorial with the string sudo, or we become root right now by typing

sudo -s

 

2 Find out if Nginx has support for GeoIP

Before we start we must find out if the GeoIP module is built into our nginx server:

nginx -V

[email protected]:~# nginx -V
nginx version: nginx/1.10.0 (Ubuntu)
built with OpenSSL 1.0.2g-fips 1 Mar 2016
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -fPIE -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -fPIE -pie -Wl,-z,relro -Wl,-z,now' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-debug --with-pcre-jit --with-ipv6 --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_addition_module --with-http_dav_module --with-http_geoip_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_image_filter_module --with-http_v2_module --with-http_sub_module --with-http_xslt_module --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-threads

 

3 Download the GeoIP Databases

On Debian and Ubuntu, there is the package geoip-database that can be installed through apt, but it is a bit outdated and only contains GeoIP.dat (country database), not GeoLiteCity.dat (city database). Therefore we don't install that package, but download fresh copies from the GeoIP web site to the /etc/nginx/geoip directory:

mkdir /etc/nginx/geoip

cd /etc/nginx/geoip
wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
gunzip GeoIP.dat.gz
wget http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
gunzip GeoLiteCity.dat.gz

 

4 Configure Nginx

Now we configure nginx. Open /etc/nginx/nginx.conf...

nano /etc/nginx/nginx.conf

... and add the geoip_country and geoip_city directives to the http {} container:

[...]
http {

##
# Basic Settings
##

geoip_country /etc/nginx/geoip/GeoIP.dat; # the country IP database
geoip_city /etc/nginx/geoip/GeoLiteCity.dat; # the city IP database [...]

The geoip_country directive makes the following variables available:

  • $geoip_country_code - two-letter country code, for example, RU, US.
  • $geoip_country_code3 - three-letter country code, for example, RUS, USA.
  • $geoip_country_name - the (verbose) name of the country, for example, Russian Federation, United States, etc.

The geoip_city directive provides the following variables:

  • $geoip_city_country_code - two-letter country code, for example, RU, US.
  • $geoip_city_country_code3 - three-letter country code, for example, RUS, USA.
  • $geoip_city_country_name - the name of the country, for example, Russian Federation, United States - if available.
  • $geoip_region - the name of region (province, region, state, province, federal land, and the like), for example, Moscow City, DC - if available.
  • $geoip_city - the name of the city, for example, Moscow, Washington, Lisbon, etc. - if available.
  • $geoip_postal_code - zip code or postal code - if available.
  • $geoip_city_continent_code - if available.
  • $geoip_latitude - latitude - if available.
  • $geoip_longitude - longitude - if available.

In order to make these variables available to your PHP scripts as well, we must set a few fastcgi_param directives. It is best to do this in the file /etc/nginx/fastcgi_params where the other fastcgi_param directives are:

nano /etc/nginx/fastcgi_params

[...]
### SET GEOIP Variables ###
fastcgi_param GEOIP_COUNTRY_CODE $geoip_country_code;
fastcgi_param GEOIP_COUNTRY_CODE3 $geoip_country_code3;
fastcgi_param GEOIP_COUNTRY_NAME $geoip_country_name;

fastcgi_param GEOIP_CITY_COUNTRY_CODE $geoip_city_country_code;
fastcgi_param GEOIP_CITY_COUNTRY_CODE3 $geoip_city_country_code3;
fastcgi_param GEOIP_CITY_COUNTRY_NAME $geoip_city_country_name;
fastcgi_param GEOIP_REGION $geoip_region;
fastcgi_param GEOIP_CITY $geoip_city;
fastcgi_param GEOIP_POSTAL_CODE $geoip_postal_code;
fastcgi_param GEOIP_CITY_CONTINENT_CODE $geoip_city_continent_code;
fastcgi_param GEOIP_LATITUDE $geoip_latitude;
fastcgi_param GEOIP_LONGITUDE $geoip_longitude;

(Make sure you have the line include /etc/nginx/fastcgi_params; in your location ~ \.php$ {} container in your vhost configuration, because otherwise, the above configuration is useless for your vhost.)

If you use nginx as a reverse proxy and want to pass the GeoIP variables to the backend, you should create/edit the file /etc/nginx/proxy.conf...

nano /etc/nginx/proxy.conf

... and add the following lines to it:

[...]
### SET GEOIP Variables ###
proxy_set_header GEOIP_COUNTRY_CODE $geoip_country_code;
proxy_set_header GEOIP_COUNTRY_CODE3 $geoip_country_code3;
proxy_set_header GEOIP_COUNTRY_NAME $geoip_country_name;

proxy_set_header GEOIP_CITY_COUNTRY_CODE $geoip_city_country_code;
proxy_set_header GEOIP_CITY_COUNTRY_CODE3 $geoip_city_country_code3;
proxy_set_header GEOIP_CITY_COUNTRY_NAME $geoip_city_country_name;
proxy_set_header GEOIP_REGION $geoip_region;
proxy_set_header GEOIP_CITY $geoip_city;
proxy_set_header GEOIP_POSTAL_CODE $geoip_postal_code;
proxy_set_header GEOIP_CITY_CONTINENT_CODE $geoip_city_continent_code;
proxy_set_header GEOIP_LATITUDE $geoip_latitude;
proxy_set_header GEOIP_LONGITUDE $geoip_longitude;

(Make sure you use the line include /etc/nginx/proxy.conf; in your nginx proxy configuration because otherwise, the backend cannot use the GeoIP variables.)

Now reload nginx...

systemctl reload nginx.service

... for the changes to take effect.

Restart PHP-FPM as follows:

systemctl restart php7.0-fpm.service

 

5 A short Test

To see if the GeoIP module is working correctly, we can create a small PHP file in our www.example.com web space (e.g. /var/www/www.example.com/web):

nano /var/www/www.example.com/web/geoiptest.php

We can access the GeoIP variables as follows:

$geoip_country_code = getenv(GEOIP_COUNTRY_CODE);

Or like this:

$geoip_country_code = $_SERVER['GEOIP_COUNTRY_CODE'];

<html>
<body>
<?php

$geoip_country_code = getenv(GEOIP_COUNTRY_CODE);
/*
$geoip_country_code = $_SERVER['GEOIP_COUNTRY_CODE']; // works as well
*/
$geoip_country_code3 = getenv(GEOIP_COUNTRY_CODE3);
$geoip_country_name = getenv(GEOIP_COUNTRY_NAME);

$geoip_city_country_code = getenv(GEOIP_CITY_COUNTRY_CODE);
$geoip_city_country_code3 = getenv(GEOIP_CITY_COUNTRY_CODE3);
$geoip_city_country_name = getenv(GEOIP_CITY_COUNTRY_NAME);
$geoip_region = getenv(GEOIP_REGION);
$geoip_city = getenv(GEOIP_CITY);
$geoip_postal_code = getenv(GEOIP_POSTAL_CODE);
$geoip_city_continent_code = getenv(GEOIP_CITY_CONTINENT_CODE);
$geoip_latitude = getenv(GEOIP_LATITUDE);
$geoip_longitude = getenv(GEOIP_LONGITUDE);

echo 'country_code: '.$geoip_country_code.'<br>';
echo 'country_code3: '.$geoip_country_code3.'<br>';
echo 'country_name: '.$geoip_country_name.'<br>';

echo 'city_country_code: '.$geoip_city_country_code.'<br>';
echo 'city_country_code3: '.$geoip_city_country_code3.'<br>';
echo 'city_country_name: '.$geoip_city_country_name.'<br>';
echo 'region: '.$geoip_region.'<br>';
echo 'city: '.$geoip_city.'<br>';
echo 'postal_code: '.$geoip_postal_code.'<br>';
echo 'city_continent_code: '.$geoip_city_continent_code.'<br>';
echo 'latitude: '.$geoip_latitude.'<br>';
echo 'longitude: '.$geoip_longitude.'<br>';

?>
</body>
</html>

Call that file in a browser (http://www.example.com/geoiptest.php), and you should see GeoIP at work (make sure that you're calling the file from a public IP address, not a local one):

GeoIP Test

It is also possible to use GeoIP variables directly in the nginx configuration, e.g. as follows:

nano /etc/nginx/sites-enabled/www.example.com.vhost

[...]
        location / {
            index index.html index.php;
            try_files /index_$geoip_country_code.html /index.html;
        }
[...]

systemctl reload nginx.service

In this example, if a visitor comes from Germany (country code: DE), and the file index_DE.html exists, then this file is served, otherwise the default index.html file is served.

This can be used to serve content in different languages, based on the user's origin.

 

Share this page:

2 Comment(s)

Add comment

Comments

From: Jinks at: 2016-07-30 16:08:54

"for example to serve content in different languages based on the user's country"

 

You deserve a slap in the face for that one.

 

No, I do not necessarily speak the the local language if the place I'm currently accessing your site from. I have set the Accept-Languag HTTP header exactly for that reason, and I expect it to be honoured, especially if it's not the default language for the region I'm currently traversing.

From: till at: 2016-08-01 06:47:07

There can be regulations depending on the country which require showing different pages. Be happy if you don't need it in your country.