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!
Hello,
when i start the script , it gives me one error:
Unknown option: S
0 entries total
Is the S option false?
Greets
what about a list for the rest of the country codes?
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.
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
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/
Hi, i saw you can also use: /usr/libexec/xtables-addons/xt_geoip_build -i /usr/share/xt_geoip/dbip-country-lite.csv
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 !
Hi Lunis, were you able to get that resolved ?, I’m having the same problem.
I had the same error after your changes. I also removed the downloads from Github and now it works
Hi, you can delete the line 334962 in the file /usr/share/xt_geoip/dbip-country-lite.csv using the following: sed -i “334962d” /usr/share/xt_geoip/dbip-country-lite.csv and try it again
Ok got it partially now working… however…
-m geoip does not seem to be an accepted argument in debian ?
You need to “modprobe xt_geoip” in order to use -m geoip.
All working for me, however, where does the log reside? i can not find it.
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
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!
How about if I wanna only allow clients from “US” and “CA”? Thanks.
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
Hi Dear, how can I block all outgoing to a country with this geoip?
and how can I make sure it’s working?
I don’t want to block incoming, I just want to block outgoing, please help me, appreciate it
Best Regards
Hello All,
I’m a bit confused. I’m with Ubuntu. How di I start iptables? Is that a daemon?
As far as I know UFW is not starting the daemon.
Tnx