Lightweight Linux Geo-blocking

A simplified guide to blocking entire countries and cybercrime IPs using IPSet. Posted 2 March 2022

Local businesses are being warned about the possibility that Russia may attack computer systems here in Jersey. I can think of a fair few reasons to be sceptical about this but anyhow, who can say for certain?

Best practice to drop connection requests from problem origins. Statistically that means China, USA, Turkey, Russia, and Brazil. You might have clients in one or more of these countries which prevents you blocking it, but you should still reduce your attack surface by dropping connections from those where you do not.

It is something you can do in a few minutes, even on a small virtual private server. Here’s how to do it on a typical Linux server running the most recent Ubuntu LTS release, 20.04.4 (Focal Fossa). This works pretty much the same across Linux distros, except you’ll have to substitute pacman or dnf for apt.


Start with the basics, you should have SSH access configured and a server user with administrative (sudo) privileges. You also need to install IPSet, in order to block huge ranges of IP addresses with minimal performance impact.

Install IPSet.

apt install ipset && mkdir /etc/ipset

Create IPSet Config Files

IPSet requires you to choose a set type, each with different purposes. We want the ‘nethash’ type, for both IPv4 and IPv6 address ranges in CIDR notation. We also want an updater script because address assignments change periodically.

Create the IPv4 & IPv6 nethashes.

ipset --create blocked-countries-ipv4 nethash
ipset --create blocked-countries-ipv6 nethash family inet6

Create a script for the initial country ‘zone’ file import, and subsequent updates.

nano /etc/ipset/ && chmod +x /etc/ipset/

Paste the following code block into the new file:


# Flush existing IPv4 rules
ipset flush blocked-countries-ipv4

# China IPv4
for ZONE in $(wget --quiet -O -
ipset --add blocked-countries-ipv4 "$ZONE"

# Russia IPv4
for ZONE in $(wget --quiet -O -
ipset --add blocked-countries-ipv4 "$ZONE"

# Flush existing IPv6 rules
ipset flush blocked-countries-ipv6

# China IPv6
for ZONE in $(wget --quiet -O -
ipset --add blocked-countries-ipv6 "$ZONE"

# Russia IPv6
for ZONE in $(wget --quiet -O -
ipset --add blocked-countries-ipv6 "$ZONE"

Here I am including both IPv4 and IPv6 zones for China and Russia, however you should adapt the script to your particular needs and threat model.

I use the address blocks supplied by IPdeny, however another good choice is this GitHub repository by Marcel Bischoff.

Now run the update script to populate the newly created nethash sets.

/bin/sh /etc/ipset/

The script may take up to a minute to run, especially if you added a lot of countries.

Integrate with existing firewall

Your firewall rules are managed by the iptables package, which is installed even if you use the simpler ufw utility to manage them.

Add the new ipset nethashes as iptables firewall rules.

iptables -A INPUT -m set --match-set blocked-countries-ipv4 src -j DROP
ip6tables -A INPUT -m set --match-set blocked-countries-ipv6 src -j DROP

Now create a systemd service file, to resume the service after your server reboots.

nano /etc/systemd/system/ipset-persistent.service

Paste the following code block into the new file:

Description=ipset persistency service

ExecStart=/sbin/ipset -exist -file /etc/ipset/ipsets.conf restore
ExecStop=/sbin/ipset -file /etc/ipset/ipsets.conf save


Now enable the new service.

systemctl daemon-reload && systemctl enable --now ipset-persistent.service

Manage Reboots and Updates

Use the cron utility for this, it’s a no-brainer. Here we restore the firewall rules after every reboot, and update the country zone files weekly.

Edit your existing crontab (or create a new one)

crontab -e

Paste the following lines into the file:

@reboot iptables -A INPUT -m set --match-set blocked-countries-ipv4 src -j DROP
@reboot ip6tables -A INPUT -m set --match-set blocked-countries-ipv6 src -j DROP
@weekly /bin/bash /etc/ipset/

That’s it. Now you block all traffic from IP address ranges assigned to particular countries. All traffic originating from those addresses is silently dropped, and this runs very efficiently.

Keep in mind that this is not a perfect security solution, and a determined attacker can use a proxy or VPN service to conceal their origin. This is just another layer in your security.

Usage and Testing

To list existing sets use ipset list -name

To view details of a list use ipset list -terse

To check a particular IP address against an existing set use ipset test <set-name> (substituting the actual set name).

Bonus Tip

You may as well add the FireHOL ‘level 1’ blacklist while you are at it. It is described as “A firewall blacklist composed from IP lists, providing maximum protection with minimum false positives. Suitable for basic protection on all internet facing servers, routers and firewalls.”

Create the FireHOL nethash.

ipset create firehol nethash

Re-open your zone file import script from earlier and paste the following code block, to integrate the new source. Note the slight difference here, we have to pipe the wget output to sed to delete comment lines from the imported file or it cannot be processed:

ipset flush firehol

for ZONE in $(wget --quiet -O - | sed '/#/d')
ipset --add firehol "$ZONE"

Run the updater again to pick up the changes:

/bin/sh /etc/ipset/

Don’t forget cron! Open cron again…

crontab -e

…and add the following line after your earlier edits:

@reboot iptables -A INPUT -m set --match-set firehol src -j DROP

Now you are blocking all traffic from multiple countries, as well as known cybercrime addresses around the world.