Block Countries by IP on Debian Trixie with nftables and xtables-addons

Debian Trixie uses nftables as its default firewall. If you’re used to iptables, the commands still work — but they go through an iptables-nft compatibility shim that translates them to nftables rules under the hood. For country-based IP blocking, the cleanest approach is xtables-addons with its built-in GeoIP module. It lets you drop entire countries at the kernel level — traffic never reaches your web server. 🔐

Check Your Current Firewall

First, confirm nftables is active:

1
nft list ruleset

If you get output (even empty table blocks), you’re on nftables. If the command isn’t found, install it:

1
2
apt install nftables
systemctl enable --now nftables

Install xtables-addons and GeoIP Tools

1
apt install xtables-addons-common libtext-csv-xs-perl curl

xtables-addons provides the -m geoip match extension. The Perl module is needed by the GeoIP database download script.

Download the GeoIP Database

The GeoIP data lives in /usr/share/xt_geoip/. A helper script builds it from the free MaxMind GeoLite2 CSV (you need a free MaxMind account for the download key):

1
2
3
4
5
# Create the directory
mkdir -p /usr/share/xt_geoip

# Download and build the binary database
/usr/lib/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip /path/to/GeoLite2-Country-CSV/

Alternatively, use the geoipupdate package with a free MaxMind license key — it handles scheduled updates automatically:

1
2
3
apt install geoipupdate
# Edit /etc/GeoIP.conf with your AccountID and LicenseKey from maxmind.com
geoipupdate

Block Countries with iptables (nft shim)

Since xtables-addons plugs into the iptables extension system, use iptables syntax with the -m geoip module. The –source-country flag takes two-letter ISO country codes:

1
2
3
4
5
# Block inbound traffic from Russia, Turkey, China, North Korea
iptables -I INPUT -m geoip --source-country RU,TR,CN,KP -j DROP

# Same for IPv6
ip6tables -I INPUT -m geoip --source-country RU,TR,CN,KP -j DROP

Check it landed:

1
iptables -L INPUT -v --line-numbers

Make It Persistent Across Reboots

iptables rules don’t survive a reboot by default. Save them:

1
2
apt install iptables-persistent
netfilter-persistent save

Rules are saved to /etc/iptables/rules.v4 and /etc/iptables/rules.v6 and restored automatically on boot.

Keep the GeoIP Database Fresh

MaxMind updates GeoLite2 twice a week. Add a cron job to refresh and reload:

1
2
3
4
# /etc/cron.weekly/update-geoip
#!/bin/bash
geoipupdate
netfilter-persistent reload
1
chmod +x /etc/cron.weekly/update-geoip

Quick Reference: ISO Country Codes

A few commonly blocked ones:

Country Code
Russia RU
Turkey TR
China CN
North Korea KP
Iran IR
Brazil BR

Full list at wikipedia.org/wiki/ISO_3166-1_alpha-2.

That’s it — once the rules are in place and persistent, your server silently drops packets from those regions before Apache or WordPress ever sees them. 🎉

How to Test and Validate the Rules

After setting up the rules, you want to confirm they actually work — not just that the commands ran without errors. Here are a few practical ways to validate. 🧪

1. Check the Rule Is Loaded

Confirm the geoip rule exists in the INPUT chain with hit counters:

1
iptables -L INPUT -v --line-numbers

Look for a line referencing geoip with your country codes. The pkts and bytes columns start at zero — they’ll increment as matching traffic hits the rule.

2. Simulate a Packet from a Blocked IP with xtables-addons

You can test whether a specific IP would be matched using iptables with the –source flag and a known IP from a blocked country. Pick a well-known public IP from that country (e.g. a Russian DNS server like 77.88.8.8 — Yandex DNS):

1
2
# Check if the rule matches a known Russian IP
iptables -C INPUT -s 77.88.8.8 -m geoip --source-country RU -j DROP

Exit code 0 means the rule matches. Exit code 1 means it doesn’t exist or doesn’t match.

3. Watch the Packet Counter Increment

Use watch to monitor the rule counters in real time while you simulate traffic:

1
watch -n1 'iptables -L INPUT -v --line-numbers'

In a second terminal, use hping3 to send a spoofed packet from a blocked IP range:

1
2
3
apt install hping3
# Send 5 SYN packets spoofed as coming from a Russian IP
hping3 -S -c 5 -a 77.88.8.8 localhost

Watch the pkts counter on the DROP rule increment in the first terminal. If it goes up, the rule is working.

4. Use a VPN to Test from a Blocked Country

The most realistic test: connect to a VPN exit node in one of your blocked countries (many free/trial VPNs have Russian or Turkish servers) and try to reach your server. You should get a connection timeout — not a refused connection, a timeout, because DROP silently discards the packet rather than sending a TCP RST back.

If you’d rather get a clear rejection instead of a silent drop, swap DROP for REJECT during testing — it sends an ICMP port-unreachable back, making it easier to confirm the block is working. Switch back to DROP for production (less information leakage).

5. Check xtables-addons GeoIP Lookup Directly

Verify the GeoIP database is loaded and resolves countries correctly:

1
2
3
4
5
6
# Check the database files exist
ls /usr/share/xt_geoip/

# Load the module manually if needed
modprobe xt_geoip
lsmod | grep geoip

If lsmod shows xt_geoip, the kernel module is loaded and the database is accessible.

Summary: Validation Checklist

Check Command Expected result
Rule exists iptables -L INPUT -v geoip DROP rule visible
Module loaded lsmod | grep geoip xt_geoip listed
DB files present ls /usr/share/xt_geoip/ .iv4/.iv6 files present
Packet counter watch iptables -L INPUT -v + hping3 pkts counter increments
Real-world test VPN to blocked country Connection timeout
This entry was posted in Linux and tagged , , , . Bookmark the permalink.

Comments are closed.