Herr Bischoff

Nginx: How to Restrict Access by Geographical Location on FreeBSD

In my setups this is mostly used to secure a backend login when a client doesn’t have access to a static IP, yet will only ever access the administration from within a certain geographical boundary, usually a country.

Start by either configuring the port to add the HTTP_GEOIP2 option (lightweight) or install the www/nginx-full package, which installs just about everything and the kitchen sink: Java, ffmpeg, what have you.

# From ports

cd /usr/ports/www/nginx
make config
# Check HTTP_GEOIP2 and accept default options for all other ports
make install clean
# From packages

pkg install nginx-full

We’d like to automatically update the GeoIP database.

pkg install geoipupdate

The package info is a bit of a mess. Correct, however, is the fact that you need to register a free account with Maxmind, which you can do here:

https://www.maxmind.com/en/geolite2/signup

Afterwards, create a license key (also free) here, substituting YOUR_USER_ID:

https://www.maxmind.com/en/accounts/YOUR_USER_ID/license-key

At the time of writing, the given location of the configuration file is wrong. The correct location is /usr/local/etc/GeoIP.conf. Add your user id and license number there. Then just run geoipupdate as root.

To finish up here, add a simple cronjob. Below is a sample crontab entry that runs geoipupdate on each Wednesday at noon:

0 12 * * 3 /usr/local/bin/geoipupdate

Now we’re equipped for the Nginx configuration. Add the following:

# /usr/local/etc/nginx/nginx.conf

load_module /usr/local/libexec/nginx/ngx_stream_module.so;
load_module /usr/local/libexec/nginx/ngx_stream_geoip2_module.so;
load_module /usr/local/libexec/nginx/ngx_http_geoip2_module.so;

[...]

http {

[...]

    geoip2 /usr/local/share/GeoIP/GeoLite2-Country.mmdb {
        $geoip2_data_country_iso_code country iso_code;
    }

    map $geoip2_data_country_iso_code $allowed_country {
        default no;
        AT yes;
        CH yes;
        DE yes;
    }

[...]

}

This enables us to use an if-statement inside a location block, like so:

location /admin/ {

    if ($allowed_country = no) {
        return 403;
    }

}

Check the configuration and restart.

nginx -t && service nginx restart

The Nginx documentation contains more information, including a rather interesting use case of choosing the nearest server based on geolocation.