A short guide on how to geo block your webpage with nginx.

Installation

If you are using the letsencrypt container the nginx module is already installed. If not you can take a look at the howtoforge guide.

That said the container doesn’t come with the GeoIP database.

1. Download the database and place it in your letsencrypt appdata location e.g /appdata/letsencrypt/geoip/GeoIPv6.dat I added a folder called geoip and placed the file there.
As geoip now is completely discontinued and being replaced with geoip2, the database is not downloadable any more. My last downloaded version can be found here: https://github.com/gilbN/Nostromo/tree/master/geoip

Note: The IPv6 database will also block IPv4!

Note2: If you use Cloudflare you have to use the IPv6 database, or you will get random 520 errors! And add set_real_ip_from x.x.x.x (See end of the post)

NGINX

2. In your nginx.conf file add the following after http {

http {

geoip_country /config/geoip/GeoIPv6.dat;

# LOCAL IP ALLOW GEO BLOCK
    geo $lan-ip {
    default no;
    192.168.1.0/24 yes;
    }
# GEO IP BLOCK SITE 1
    map $geoip_country_code $allowed_country {
    default no;
    YOUR-COUNTRY-CODE yes; # e.g <US> for United States 
    }

Instead of "YOUR-COUNTRY-CODE" add your own country code from this list. This will block all other countries than the one you choose.

The geo $lan-ip is for allowing you to access the domain on your LAN.
Check out https://www.aelius.com/njh/subnet_sheet.html if you are wondering what your CIDR notation is. Most often it will be /24 (netmask 255.255.255.0)
To find your netmask run ipconfig /all on windows or ifconfig | grep netmask on linux.

Note: The geo $lan-ip part is only needed if you set default to no

For it to actually block you need to add this in your server block:

 # LOCAL IP ALLOW GEO BLOCK
    if ($lan-ip = yes) {
    set $allowed_country yes;
    }
    
# COUNTRY GEO BLOCK 
    if ($allowed_country = no) {
    return 444;
    }

3. If you host several websites and want a different country block just add another with a different variable ($allowed_country2):

As defaultis set to yes it will allow every country except the country codes set to no

 # GEO IP BLOCK DOMAIN 2
  map $geoip_country_code $allowed_country2 {
   default yes;
    CN no; #China
    RU no; #Russia
    HK no; #Hong Kong
    IN no; #India
    IR no; #Iran
    VN no; #Vietnam
    TR no; #Turkey
    EG no; #Egypt
    MX no; #Mexico
    JP no; #Japan
    KR no; #South Korea
    KP no; #North Korea 🙂
    PE no; #Peru
    BR no; #Brazil
    UA no; #Ukraine
    ID no; #Indonesia
    TH no; #Thailand
 }

I made this list based on the Spamhaus statistics and Aakamai’s state of the internet report.

Then add this in your other server block

#COUNTRY GEO BLOCK
    if ($allowed_country2 = no) {
    return 444;
}

Blocked

You can test if it worked with a VPN or do a performance test from a location that is blocked here https://www.webpagetest.org/

TIP! This database does get updated!

By using the User Scripts plugin you can easily set up a cronjob every week.
Click on Add New Script → Enter the name → Click on the name of the script and then Edit Script

Copy the script and set your download path. (Thank you Tronyx for the script!)

#!/usr/bin/env bash

wget -O /tmp/GeoIPv6.dat.gz http://geolite.maxmind.com/download/geoip/database/GeoIPv6.dat.gz

gunzip /tmp/GeoIPv6.dat.gz

mv /tmp/GeoIPv6.dat /mnt/cache/appdata/letsencrypt/geoip/GeoIPv6.dat

chown 911:911 /mnt/cache/appdata/letsencrypt/geoip/GeoIPv6.dat

Set the schedule to custom and add 00 00 * * 1 for it to run every monday at 00:00

Click Run In Background and then Apply

 

Cloudflare

If you use Cloudflare you will need to add the following in nginx so that it won’t block the Cloudflare IP but the actual IP of the visitor. If you don’t do this you will get false positives.

Add the following section to your  nginx.conf file found in appdata/letsencrypt/nginx/

set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;

real_ip_header X-Forwarded-For;

You can set it up as an include like this too:

##
# CF Real IP
##
include /config/nginx/cf_real-ip.conf;
real_ip_header X-Forwarded-For;

After that you need to restart the letsencrypt container for the changes to take effect.

Automatically updating the cf_real-ip.conf

Tronyx over at the discord forums graciously shared his script for updating the Cloudflare ip list. This list is not static and Cloudflare updates it once in a while.

The script below will update the list with any changes in the list of IPs from CF

#!/usr/bin/env bash

curl -s 'https://www.cloudflare.com/ips-v4' > /tmp/cf.txt
curl -s 'https://www.cloudflare.com/ips-v6' >> /tmp/cf.txt

sed 's/^/set_real_ip_from /' /tmp/cf.txt > /tmp/cf2.txt

sed -e 's/$/;/' /tmp/cf2.txt > /home/letsencrypt/config/nginx/cf_real-ip.conf

I use unraid so I will use the User Scripts plugin to setup a cronjob that will run once a week.

  1. Go to Settings > User Scripts and click Add New Script 
  2. Give it a name and a description if you want
  3. Click edit script and add it inside the box.
  4. You will need to update the path in the last line to you nginx appdata folder
  5. Set the schedule to weekly and click apply and done.
    #!/bin/bash
    curl -s 'https://www.cloudflare.com/ips-v4' > /tmp/cf.txt
    curl -s 'https://www.cloudflare.com/ips-v6' >> /tmp/cf.txt
    
    sed 's/^/set_real_ip_from /' /tmp/cf.txt > /tmp/cf2.txt
    
    sed -e 's/$/;/' /tmp/cf2.txt > /mnt/user/appdata/letsencrypt/nginx/cf_real-ip.conf

    Souce:

    https://dev.maxmind.com/geoip/legacy/install/country/
    http://dev.maxmind.com/geoip/legacy/codes/iso3166/
    https://www.howtoforge.com/nginx-how-to-block-visitors-by-country-with-the-geoip-module-debian-ubuntu
    https://www.spamhaus.org/statistics/botnet-cc/
    https://www.akamai.com/us/en/multimedia/documents/state-of-the-internet/q3-2017-state-of-the-internet-security-report.pdf