How to Block Traffic by Country on a Linux Firewall Using GeoIP Rules
Level: Professional
Estimated time: ~35 minutes
Goal: Configure geoip firewall rules on a Linux server to restrict access by country, using iptables with the xtables-geoip module or nftables with IP sets - and automate updates so the lists stay current.
Introduction
If your services target a specific region, there is no reason to accept traffic from the rest of the world. Linux firewall geo blocking lets you drop packets by source country before they reach your application - cutting brute-force noise and reducing attack surface. In this tutorial, you will block traffic by country on a Linux firewall using geoip firewall rules: first with iptables and xtables-addons, then with nftables and CIDR-based IP sets, including automated database updates to keep your ip blocking by country firewall accurate.
Prerequisites
Before you begin, make sure the following conditions are met:
- Operating system: Ubuntu 22.04/24.04 LTS, Debian 12/13, AlmaLinux/RHEL 9/10
- Access: sudo or root access to the server
- Firewall: iptables 1.8+ with xtables-addons, or nftables 1.0+
- Required packages:
curl,iptables,linux-headers-$(uname -r)(Debian/Ubuntu); EPEL repository enabled (RHEL/AlmaLinux 9)
On Debian 12+, Ubuntu 22.04+, and RHEL/AlmaLinux 9+, the iptables command is a wrapper around the nftables backend (iptables-nft). The -m geoip match works correctly with this backend.
- Network: a clear understanding of which countries your services need to accept traffic from
- Required knowledge: confident use of the Linux command line, basic firewall concepts, and file editing
- Backup: a working out-of-band console (IPMI/KVM) or provider rescue mode in case a rule locks you out
Applying country-level firewall rules over an SSH session is inherently risky. If you block your own country by mistake, you lose access. Always have an out-of-band fallback ready, and test rules before making them permanent.
Step 1: Identify your allowed and blocked countries
Before touching the firewall, decide on your policy. There are two approaches:
- Allowlist (recommended): Allow only the countries you need, drop everything else. This is stricter and more secure.
- Blocklist: Allow everything, drop specific countries. Simpler but leaves more surface area.
For this tutorial, we will use the allowlist approach - it aligns with the principle of least privilege. Suppose your services only serve users from the US, Germany, and Poland. The two-letter ISO country codes are: US, DE, PL.
Country codes follow the ISO 3166-1 alpha-2 standard. Find the full list at https://www.iso.org/obp/ui/#search/code/. Be precise - a typo here silently blocks legitimate traffic.
Step 2: Set up GeoIP with iptables (xtables-addons)
This method uses the xt_geoip module from xtables-addons, which adds the -m geoip match to iptables. This is one of the most established ways to block ip range by country on linux.
2.1 Install xtables-addons and dependencies
Debian/Ubuntu:
sudo apt install -y linux-headers-$(uname -r) sudo apt update && sudo apt install -y \ xtables-addons-common libtext-csv-xs-perl unzip iptables curl
xtables-addons is not available in AlmaLinux 10 repositories. Use the nftables method (Step 3) instead, or build xtables-addons from source.
After installation, verify the module is available:
modinfo xt_geoip
Expected output includes a line such as:
filename: /lib/modules/.../xt_geoip.ko description: Xtables: country matching via GeoIP
If modinfo returns an error, the module did not install correctly. Check that your kernel headers match the running kernel version.
2.2 Download and build the GeoIP database
The xt_geoip module requires a local database of country-to-IP mappings. These ship as CSV files and must be converted to a binary format.
Create the working directory and the database directory:
sudo mkdir -p /usr/share/xt_geoip
Download the latest GeoIP CSV data. The upstream source provides the data through the xtables-addons tooling:
cd /tmp /usr/libexec/xtables-addons/xt_geoip_dl
Convert the downloaded CSV to binary format:
/usr/libexec/xtables-addons/xt_geoip_build -D /usr/share/xt_geoip *.csv
Expected output - a list of country codes being processed:
4540 IPv4 ranges for ZA 1071 IPv6 ranges for ZA 147 IPv4 ranges for ZW 94 IPv6 ranges for ZW ...
2.3 Apply iptables rules with GeoIP matching
Now create the actual geoip firewall rules. The following script allows traffic from your chosen countries and drops everything else on the INPUT chain.
Back up your current rules first:
sudo iptables-save > /tmp/iptables-backup-$(date +%Y%m%d).rules
Apply the country allowlist:
# Allow loopback sudo iptables -A INPUT -i lo -j ACCEPT # Allow established and related connections sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # Allow traffic from US, DE, PL only sudo iptables -A INPUT -m geoip --src-cc US,DE,PL -j ACCEPT # Log dropped packets sudo iptables -A INPUT -j LOG --log-prefix "GEOIP-DROP: " --log-level 4 # Drop everything else sudo iptables -A INPUT -j DROP
The final DROP rule blocks all traffic not matching the allowlist. Make sure your SSH source country is included, or you will be locked out immediately. If you are unsure, add a temporary ACCEPT rule for your IP before the DROP: sudo iptables -I INPUT -s <YOUR_IP_ADDRESS> -j ACCEPT.
Verify the rules are active:
sudo iptables -L INPUT -v --line-numbers
Expected output:
Chain INPUT (policy ACCEPT) num target prot opt source destination 1 ACCEPT all -- anywhere anywhere /* loopback */ 2 ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED 3 ACCEPT all -- anywhere anywhere -m geoip --source-country US,DE,PL 4 LOG all -- anywhere anywhere LOG level warning prefix "GEOIP-DROP: " 5 DROP all -- anywhere anywhere
2.4 Persist iptables rules across reboots
Rules applied with iptables are lost on reboot. Save them:
Debian/Ubuntu:
sudo apt install iptables-persistent -y sudo netfilter-persistent save
AlmaLinux/RHEL:
sudo service iptables save
Step 3: Set up GeoIP with nftables (IP sets)
If your system uses nftables (the default on Debian 11+, Ubuntu 22.04+, and RHEL 9+), you can achieve the same result using IP sets populated with country-level CIDR blocks. This approach does not require kernel modules beyond what nftables already provides.
3.1 Obtain country IP ranges
Download CIDR lists for the countries you want to allow. Several public sources provide these, such as ipdeny.com:
sudo mkdir -p /etc/nftables/geoip cd /etc/nftables/geoip # Download CIDR blocks for US, DE, PL for CC in us de pl; do sudo curl -sS -o /etc/nftables/geoip/${CC}.zone \ "https://www.ipdeny.com/ipblocks/data/aggregated/${CC}-aggregated.zone"; done
Verify the files contain CIDR ranges:
head -5 /etc/nftables/geoip/us.zone
Expected output (CIDR ranges, one per line):
1.178.0.0/23 1.178.4.0/22 1.178.8.0/21 ...
3.2 Build and load the nftables configuration
Create a script that reads the CIDR files and generates an nftables set. Save it as /etc/nftables/geoip-update.sh:
sudo nano /etc/nftables/geoip-update.sh
Paste the following:
#!/bin/bash # Generate nftables set from country CIDR files set -euo pipefail GEOIP_DIR="/etc/nftables/geoip" OUTPUT="/etc/nftables/geoip-sets.nft" echo "define ALLOWED_COUNTRIES = {" > "$OUTPUT" for zone_file in "$GEOIP_DIR"/*.zone; do while IFS= read -r cidr; do && continue echo " ${cidr}," >> "$OUTPUT" done < "$zone_file" done echo "}" >> "$OUTPUT" echo "GeoIP set generated: $(wc -l < "$OUTPUT") lines"
Make it executable:
sudo chmod +x /etc/nftables/geoip-update.sh
Run the script:
sudo /etc/nftables/geoip-update.sh
Now reference this set in your nftables configuration:
# Debian/Ubuntu: sudo nano /etc/nftables.conf # AlmaLinux/RHEL: sudo nano /etc/sysconfig/nftables.conf
Add the include and use the set in your input chain:
#!/usr/sbin/nft -f flush ruleset include "/etc/nftables/geoip-sets.nft" table inet filter { chain input { type filter hook input priority 0; policy drop; ct state invalid drop iif lo accept ct state established,related accept # Allow ICMP ip protocol icmp accept ip6 nexthdr icmpv6 accept # GeoIP: allow only listed countries ip saddr $ALLOWED_COUNTRIES accept # Log dropped packets log prefix "geoip-drop: " level info # Everything else is dropped by policy } chain forward { type filter hook forward priority 0; policy drop; } chain output { type filter hook output priority 0; policy accept; } }
Validate the syntax before applying:
sudo nft -c -f /etc/nftables.conf
If no errors appear, apply and verify:
sudo nft -f /etc/nftables.conf sudo systemctl enable --now nftables sudo nft list ruleset | head -20
For very large IP sets (100K+ entries), nftables handles them significantly better than iptables. The set-based matching in nftables uses hash maps internally, so lookup performance stays constant regardless of set size.
Step 4: Automate GeoIP database updates
IP-to-country mappings change constantly. A database from last month will have gaps. Schedule automatic updates with cron.
4.1 Create the update script
Create /usr/local/sbin/geoip-refresh.sh:
sudo nano /usr/local/sbin/geoip-refresh.sh
For iptables (xtables-addons):
#!/bin/bash set -euo pipefail cd /tmp /usr/libexec/xtables-addons/xt_geoip_dl /usr/libexec/xtables-addons/xt_geoip_build \ -D /usr/share/xt_geoip *.csv rm -f /tmp/*.csv /tmp/*.zip logger "GeoIP database updated successfully"
For nftables:
#!/bin/bash set -euo pipefail GEOIP_DIR="/etc/nftables/geoip" for CC in us de pl; do curl -sS -o "${GEOIP_DIR}/${CC}.zone" \ "https://www.ipdeny.com/ipblocks/data/aggregated/${CC}-aggregated.zone" done /etc/nftables/geoip-update.sh systemctl reload nftables logger "GeoIP nftables sets updated and reloaded"
Make it executable:
sudo chmod +x /usr/local/sbin/geoip-refresh.sh
4.2 Schedule with cron
Before scheduling the cron task, ensure the nftables service is enabled and running (if using the nftables method):
sudo systemctl enable --now nftables
Run the update weekly:
sudo crontab -e
Add:
0 3 * * 0 /usr/local/sbin/geoip-refresh.sh >> /var/log/geoip-update.log 2>&1
This runs every Sunday at 3:00 AM. After the first scheduled run, verify it worked:
cat /var/log/geoip-update.log
Step 5: Verification and testing
5.1 Test from an allowed country
From a machine in one of your allowed countries, connect via SSH or make an HTTP request:
curl -v http://<YOUR_SERVER_IP>
The connection should succeed normally.
5.2 Simulate a blocked country
Use curl with a known IP from a blocked country through a proxy, or test locally by temporarily removing your own country from the allowlist and attempting a connection from a second terminal.
Online services like https://ipinfo.io let you verify which country a given IP is registered to. Use curl https://ipinfo.io/<YOUR_IP_ADDRESS> to confirm.
5.3 Check firewall counters
iptables:
sudo iptables -L INPUT -v -n
Look at the packet counters on the DROP rule - they should be incrementing as blocked traffic arrives.
nftables:
sudo nft list chain inet filter input
5.4 Monitor system logs
Check for dropped packets in the system log:
sudo journalctl -k --since "1 hour ago" | grep -i "geoip-drop"
If you observe a high volume of kernel log messages about dropped packets and your system becomes unresponsive, a logging flood may be the cause. In the worst case, this can trigger a kernel panic. If you have configured crashkernel parameter and the kdump service, a kernel panic will produce a crash dump (vmcore) in /var/crash that you can analyze later. Without kdump, you only get a reboot and no diagnostics. See the section below for a brief overview.
Step 6: Protecting against kernel panic during firewall changes
Aggressive firewall changes on a busy production server - especially those that cause sudden traffic spikes or logging floods - can in rare cases lead to a kernel panic. If that happens and you have not prepared, you get nothing: just a reboot and no data about what went wrong.
Kdump is the standard Linux mechanism for capturing a crash dump when a kernel panic occurs. It uses kexec to boot a secondary capture kernel, which writes the memory image (vmcore) to disk before the system restarts.
To ensure you have diagnostics available:
- Verify kdump is installed and enabled. On RHEL/AlmaLinux, it is typically pre-installed. On Debian/Ubuntu:
sudo apt install kdump-tools kexec-tools -y
- Check that the
crashkernelparameter is set in your bootloader configuration:
You should see something likecat /proc/cmdline | grep crashkernel
crashkernel=256M. If it is missing, you need to configure crashkernel parameter in GRUB and reboot. The exact amount to reserve memory for kdump linux depends on your total RAM -256Mis a safe default for servers with 4 GB or more. - Verify the kdump service is active:
sudo systemctl status kdump
If a kernel panic does occur, the system will use kexec to boot the capture kernel, write the vmcore to /var/crash, and then reboot normally. You can then perform vmcore analysis linux using the crash utility to determine the root cause. This is a standard part of linux kernel panic troubleshooting on production systems. For a complete guide on how to enable kdump in linux and kexec kdump linux configuration, refer to a dedicated kdump tutorial.
Troubleshooting
- Locked out via SSH: Use out-of-band console / rescue mode.
modinfo xt_geoip: not found: Installlinux-headers-$(uname -r).iptables: command not found(Debian 13): Install iptables withsudo apt install iptables.nftables.serviceis not active: Runsudo systemctl enable --now nftables.- Empty
/etc/sysconfig/nftables.confon AlmaLinux: Use this path instead of/etc/nftables.confto configure rules.
Rollback
To undo country-based blocking and restore open access:
iptables - restore from backup:
sudo iptables-restore < /tmp/iptables-backup-*.rules
If you applied GeoIP rules on a server with no prior firewall configuration, the backup file will be empty. In that case, use the flush-and-policy commands below instead of iptables-restore.
Or flush all rules:
sudo iptables -F INPUT sudo iptables -P INPUT ACCEPT
Flushing to ACCEPT removes all firewall protection. Apply your standard security ruleset immediately after.
nftables - restore default:
sudo nft flush ruleset
Then restore your base nftables configuration (without GeoIP):
sudo nft -f /etc/nftables.conf.backup
To remove the GeoIP components entirely:
sudo rm -rf /etc/nftables/geoip sudo rm -f /etc/nftables/geoip-sets.nft /etc/nftables/geoip-update.sh sudo crontab -l | grep -v geoip-refresh | sudo crontab -
Remove xtables-addons (if installed):
Debian/Ubuntu:
sudo apt remove xtables-addons-common -y
AlmaLinux/RHEL:
sudo dnf remove xtables-addons -y
Conclusion
Your server now uses geoip firewall rules to block traffic by country on linux - either through iptables with xt_geoip or nftables with CIDR-based IP sets. The database updates automatically, you have a rollback plan, and with kdump configured for crash dump capture in case of a kernel panic, your production environment is covered from both routine threats and worst-case scenarios.
Document Version: 1.0
Last Updated: May 2026
Owner: Technical Documentation Team