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 |