[Guide] Geo Blocking With UFW / IPTables

Geo blocking are sometimes necessary for some web application to filtered out traffic from countries or simply to reduce cost by reduce the usage from non targeted countries.

This guide will show you how to setup geo blocking with firewall rules to block user based on their countries, we will be using the geoip module from Xtables-addons, and we will be merging multiple free database to get more comprehensive coverage.

In this tutorial my environment is Ubuntu 20.04 LTS, and the example of this tutorial will be blocking users from certain countries from accessing our web app hosted on this server.

Update 2 Aug 2022: If you have trouble following this guide, or using the GeoIP module, we have made another blog post that uses the Ipset module to block countries, VPN, etc.

Install Prerequisites

sudo apt update -y
sudo apt upgrade -y
sudo apt install curl perl unzip xtables-addons-common libtext-csv-xs-perl libmoosex-types-netaddr-ip-perl

Download Database

First, let’s make a directory:

sudo mkdir -p /usr/share/xt_geoip

Now we’ll setup a script to download and update database for the latest IP to countries list, this is a simple script that combine multiple source of IP2Country databases, for better coverage you can also pay for DP-IP commercial database, which will include more IPs.

sudo nano /usr/local/bin/update-geoip.sh

Then paste this into the script and save:

#!/bin/bash

# Create temporary directory
mkdir -p /usr/share/xt_geoip/tmp/
mkdir -p /usr/share/xt_geoip/tmp/ip2loc/

# Download latest from db-ip.com
cd /usr/share/xt_geoip/tmp/
/usr/lib/xtables-addons/xt_geoip_dl

# Download maxmind legacy csv and process
wget https://mailfud.org/geoip-legacy/GeoIP-legacy.csv.gz -O /usr/share/xt_geoip/tmp/GeoIP-legacy.csv.gz
gunzip /usr/share/xt_geoip/tmp/GeoIP-legacy.csv.gz
cat /usr/share/xt_geoip/tmp/GeoIP-legacy.csv | tr -d '"' | cut -d, -f1,2,5 > /usr/share/xt_geoip/tmp/GeoIP-legacy-processed.csv
rm /usr/share/xt_geoip/tmp/GeoIP-legacy.csv
rm /usr/share/xt_geoip/tmp/GeoIP-legacy.csv.gz

# Download latest from https://github.com/sapics/ip-location-db
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/geo-whois-asn-country/geo-whois-asn-country-ipv4.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/geo-whois-asn-country/geo-whois-asn-country-ipv6.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/iptoasn-country/iptoasn-country-ipv4.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/iptoasn-country/iptoasn-country-ipv6.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/dbip-country/dbip-country-ipv4.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/dbip-country/dbip-country-ipv6.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/geolite2-country/geolite2-country-ipv4.csv
wget -P /usr/share/xt_geoip/tmp/ https://cdn.jsdelivr.net/npm/@ip-location-db/geolite2-country/geolite2-country-ipv6.csv

# Combine all csv and remove duplicates
cd /usr/share/xt_geoip/tmp/
cat *.csv > geoip.csv
sort -u geoip.csv -o /usr/share/xt_geoip/dbip-country-lite.csv

# Remove temp directory and update geoip xtables
rm -r /usr/share/xt_geoip/tmp/
/usr/lib/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip/ -S /usr/share/xt_geoip/
rm /usr/share/xt_geoip/dbip-country-lite.csv

# reload ufw
# ufw reload

Now make the script executable and run the script for the first time to download database

sudo chmod +x /usr/local/bin/update-geoip.sh
/usr/local/bin/update-geoip.sh

Once it’s completed you should see these database sorted by countries and IP type in /usr/share/xt_geoip, you should also setup a cronjob to update this database once a month.

Using GeoIP with UFW for Geo Blocking

Now we need to add custom firewall rules to use the geoip module blocking user from certain countries, first we need to modify this 2 file, the first is for ipv4 and the second is ipv6

  • /etc/ufw/before.rules
  • /etc/ufw/before6.rules

This is an example how to use them, just add the rules into the chain, for example to block known IPs from Russian and Ukraine accessing our web app which is listening on port 3000:

-A ufw-before-input -p tcp --dport 3000 -m geoip --src-cc RU,UA -j DROP

I have mine setup to only start geo blocking at certain hours and also keeping logs:

-A ufw-before-input -p tcp --dport 3000 -m time --timestart 05:00 --timestop 16:00 -m geoip --src-cc RU,UA,A1 -j LOG --log-prefix "[BLOCKED COUNTRIES] "
-A ufw-before-input -p tcp --dport 3000 -m time --timestart 05:00 --timestop 16:00 -m geoip --src-cc RU,UA,A1 -j DROP

The A1 are from maxmind, which is known IPs from anonymizing services such as proxies and VPN.

Want someone to set this up for you?

If your using our managed WordPress hosting we are happy to help you set this up for you!

17 thoughts on “[Guide] Geo Blocking With UFW / IPTables”

  1. It seems that in Ubuntu 20.4LTS the perl script “/usr/lib/xtables-addons/xt_geoip_build” needs to be called using “perl /usr/lib/xtables-addons/xt_geoip_build” otherwise one gets a “permission denied” error, even when executing the script as a superuser. I don’t know if anyone else can replicate this error but this did the trick for me.

    Thanks for the tidy instructions.

    Reply
  2. This part is where it always fail for me.
    /usr/lib/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip/ -S /usr/share/xt_geoip/

    My xtables-addons is located in /usr/libexec so I upated the sh file to match the correct directory. But it’s now outputting that Unknown command S which I guess pertains to the “-S” part of the sh file.

    I’m currently using Debian 11

    Reply
  3. Confused: run
    cd /usr/share/xt_geoip/tmp/
    /usr/libexec/xtables-addons/xt_geoip_build
    instead of
    /usr/lib/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip/ -S /usr/share/xt_geoip/

    Reply
    • Hi, i saw you can also use: /usr/libexec/xtables-addons/xt_geoip_build -i /usr/share/xt_geoip/dbip-country-lite.csv

      Reply
  4. Hi I can help with some of this.

    xt_geoip_build only understands the following (one letter) arguments:

    -i (not sure what this is for)
    -q (for Quiet)
    -s
    -D (Directory)

    So what I did was the following:

    cd /usr/share/xt_geoip
    /usr/libexec/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip/ -s /usr/share/xt_geoip/

    Please note that on Debian its libexec not lib…

    Still I get an error after counting the rows:

    331776 entriesStart IP is greater than end IP at /usr/share/perl5/Net/CIDR/Lite.pm line 255, line 334962.
    Net::CIDR::Lite::add_range(Net::CIDR::Lite=HASH(0x55680ac5d210), “2001:1900:5:2:2:0:5a20::-2001:1900:5:2:2:0:c:227f”) called at /usr/libexec/xtables-addons/xt_geoip_build line 63
    main::collect() called at /usr/libexec/xtables-addons/xt_geoip_build line 37

    Any help is much appreciated !

    Reply
  5. At first THANK YOU for this tutorial and the script. All the other stuff out there is not working anymore and got me hours wasting with trying to get it work.

    I adjusted your script a bit to make it more safe to execute it and I fixed some errors.

    – I added “-e” to the shebang to stop in case of errors
    – I added “-f” to all “rm” commands to not stop the script in case the file has been removed already
    – I adjusted the paths for DEBIAN (/usr/libexec/ instead of /usr/lib)
    – I added “-n” to the sort command to solve the problem reported by Lunis
    – I changed the final command to work “/usr/libexec/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip/ -i /usr/share/xt_geoip/dbip-country-lite.csv”

    Find the full script here:
    https://0bin.net/paste/EnoqKN5v#CsCNgcEFCg+j1WoXUMdC53Ndj1llaqMykabcewrIpyy

    Reply
  6. For people having issues with the xt_geoip_build on Ubuntu:

    it seems that parameters for this command changed.
    The line should look like this:

    /usr/libexec/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip -i /usr/share/xt_geoip/dbip-country-lite.csv

    Tested on Ubuntu 22.04

    To deal with the IPv6 errors “Start IP is greater than end IP” I’ve commented the wget lines with *-ipv6.csv files. I don’t need IPv6, so no need for them. No more errors

    Anyway, big thanks for this tutorial!

    Reply
    • You just allow those countries then drop everything else like below (assuming for port 3000 TCP)

      -A ufw-before-input -p tcp --dport 3000 -m geoip --src-cc US,CA -j ACCEPT
      -A ufw-before-input -p tcp --dport 3000 -j DROP

      Reply

Leave a Comment