RT Cunningham

A GeoLite Legacy GeoIP Country Alternative for Nginx


The original GeoIP module for Nginx is effectively useless. If you visit the MaxMind GeoLite2 free downloads page today, you’ll see a note at the top stating the GeoLite legacy databases were discontinued on January 2, 2019.

You can follow the Nginx instructions for using the GeoLite2 database in various places but I have a much better idea: Use something a bit more reliable.

A GeoIP Country Alternative

The way the GeoLite and GeoLite2 databases work is that they identify the geographical location where certain IP addresses are used. They’re not always correct. MaxMind even says as much. They say the paid database is much more accurate. I don’t trust either one.

Why not just identify where the IP addresses are allocated using the various internet registry services like AFRINIC, APNIC, ARIN, LACNIC and RIPE? Hetzner Online, for example, is based in Germany and is listed with the RIPE database. Nobis Technology is listed with the ARIN database. It seems to me to be the most accurate way of identifying IP addresses belonging to data centers.

To find out which IP addresses belong to which country, you need to get the data from somewhere. I use IPdeny and I think it’s pretty accurate. A cron job executes a script every night, just after midnight California time (my server time) and builds a “geo ” file for my Nginx web server. The script looks like this:

shell_exec('wget http://www.ipdeny.com/ipblocks/data/countries/all-zones.tar.gz');
shell_exec('tar xvzf all-zones.tar.gz');
$newline[] = "geo \$cc {\ndefault 00;\n";
foreach (glob("*.zone") as $filename) {
$a = explode('.', $filename);
$file = file($filename);
foreach ($file as $line) {
if ($line != '') {
$line = trim($line);
$newline[] = $line . ' ' . strtoupper($a[0]) . ";\n";
$newline[] = "}\n";
file_put_contents('/etc/nginx/conf.d/countries.conf', $newline);
shell_exec('rm *.zone');
shell_exec('rm *.gz');

I’ve been using this script for at least two weeks and it hasn’t failed me yet.

My Nginx Server Code

As you can imagine, the above script sorts and assigns the two character country codes from each file name. The next step is to get the web server to recognize them. The Nginx variable is “$cc”, which I “include” in the nginx.conf with:

include /etc/nginx/conf.d/countries.conf;

I then tack the country code onto the end of each access log entry. This is the code for my access log:

log_format mine '$host - $remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" "$cc"';
map $status $loggable {
default 1;
~400 0;
~405 0;
access_log /var/log/nginx/access.log mine if=$loggable;

I now use “$cc” instead of “$geoip_country_code” in the scripts I write. The script I use to parse my access log is just one example. I sort it by country code and then IP address, which makes it easy to spot unidentified or unauthorized bots. Of course, I’ll have to update any examples I’ve written about (like “GDPR Revisited – How to Block those European Union Countries“).

The way I now identify GeoIP countries doesn’t require downloading any database from MaxMind, which is extremely okay with me. If the IPdeny website disappears, I’m sure I can find the same data elsewhere. I don’t like having to rely on a specific service – I don’t think anyone should have to.


RT Cunningham
March 16, 2019
Web Development