Skip to content

Instantly share code, notes, and snippets.

@asheroto
Last active October 8, 2025 01:08
Show Gist options
  • Save asheroto/934e056a302adda334077f0c85cfe4b4 to your computer and use it in GitHub Desktop.
Save asheroto/934e056a302adda334077f0c85cfe4b4 to your computer and use it in GitHub Desktop.
Easily install and configure GeoIP for use with iptables which enables you to block/allow entire countries.

Configure GeoIP for iptables

Important

This setup forces iptables-legacy (not nftables) and installs a custom-built xt_geoip_query tool written in C to properly read modern GeoIP databases (xtables-addons ≥ 3.20). The script does not modify firewall rules automatically; it only echoes recommended examples.

Caution

If you use Docker or containerized services, switching to iptables-legacy can temporarily break container networking. This happens because Docker manages its own NAT and MASQUERADE rules, which can be lost when iptables backends change or modules are reloaded. SEE NOTES BELOW.


This script automates configuring GeoIP filtering for iptables. It installs dependencies, builds and loads the xt_geoip kernel module, compiles a working GeoIP lookup utility, and verifies that lookups return the correct ISO country code.

Supports colored terminal output using ANSI escape codes. 😎

Run the script as root or with sudo.


Why a Custom xt_geoip_query?

Starting with xtables-addons 3.20, the binary format of GeoIP .iv4 and .iv6 files changed slightly. The original xt_geoip_query shipped with older versions fails to read these files correctly on modern systems, returning incorrect country codes (e.g., AE instead of US for Google DNS).

To fix this, the installer compiles a custom C utility that reads the modern binary structure directly, without relying on outdated headers or deprecated logic. This ensures accurate results for both IPv4 and IPv6 lookups and aligns with the endian layout used by current database builders (xt_geoip_build).


Supported Distributions

This script supports the following distributions (and derivatives):

Distro Family Package Manager Tested On Notes
Debian / Ubuntu apt Debian 12 (Bookworm), Ubuntu 22.04+ Installs xtables-addons-dkms & headers automatically
RHEL / CentOS / Fedora dnf AlmaLinux 9, Fedora 39 Uses system kernel-devel for matching build
Arch / Manjaro pacman Arch Linux (2025.09 snapshot) Requires base-devel & linux-headers
Others May work if xtables-addons and xt_geoip are available and compatible

Script Functionality

  • Installs required build tools and dependencies
  • Ensures the system is using the iptables-legacy backend (not nftables)
  • Installs matching Linux kernel headers
  • Downloads and compiles xtables-addons version 3.25 from inai.de
  • Verifies that /usr/share/xt_geoip contains valid .iv4 / .iv6 GeoIP binary files
  • Builds and installs /usr/local/bin/xt_geoip_query, a custom binary parser for .iv4 / .iv6 files
  • Performs a quick lookup test using 8.8.8.8 — if successful, it should display US
  • Echoes example iptables rules for allowing local and U.S. traffic (no rules are actually applied)

Example iptables Commands

Allow Only U.S. and Local Traffic

iptables -F
iptables -I INPUT -i lo -j ACCEPT
iptables -I INPUT -s 10.0.0.0/8 -j ACCEPT
iptables -I INPUT -s 172.16.0.0/12 -j ACCEPT
iptables -I INPUT -s 192.168.0.0/16 -j ACCEPT
iptables -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -I INPUT -m geoip --src-cc US -j ACCEPT
iptables -P INPUT DROP

Docker containers (filter container ingress via DOCKER-USER)

DOCKER-USER is evaluated before Docker’s own rules.

# Create (if missing) and clear DOCKER-USER
iptables -N DOCKER-USER 2>/dev/null || true
iptables -F DOCKER-USER

# Allow established and local RFC1918 sources
iptables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -I DOCKER-USER 2 -s 10.0.0.0/8 -j ACCEPT
iptables -I DOCKER-USER 3 -s 172.16.0.0/12 -j ACCEPT
iptables -I DOCKER-USER 4 -s 192.168.0.0/16 -j ACCEPT

# Allow only US; everything else to containers is dropped
iptables -I DOCKER-USER 5 -m geoip --src-cc US -j ACCEPT
iptables -A DOCKER-USER -j DROP

Block a Specific Country

iptables -I INPUT -m geoip --src-cc XX -j DROP

Replace XX with the ISO 3166-1 alpha-2 country code to block.

Block All Countries Except One

iptables -I INPUT -m geoip ! --src-cc YY -j DROP

Replace YY with the country code you want to allow.


References

#!/bin/bash
# =========================================================
# xt_geoip + iptables installer (with fixed C query tool)
# ---------------------------------------------------------
# • Builds and installs xtables-addons + xt_geoip module
# • Compiles a working xt_geoip_query (matches ≥3.20 layout)
# • Detects system type and installs dependencies
# • Verifies GeoIP .iv4/.iv6 binary database
# • Does NOT apply iptables rules — only echoes them
# =========================================================
set -e
XTA_VERSION="3.25"
GEOIP_DIR="/usr/share/xt_geoip"
echo -e "\033[32m[1/7] Checking dependencies...\033[0m"
source /etc/os-release
# -------------------------------
# Install dependencies per distro
# -------------------------------
if [[ "$ID" =~ (debian|ubuntu) || "$ID_LIKE" =~ debian ]]; then
apt update
apt install -y build-essential curl unzip pkg-config \
xtables-addons-dkms xtables-addons-common \
linux-headers-$(uname -r) perl libtext-csv-xs-perl \
libmoosex-types-netaddr-ip-perl jq
elif [[ "$ID" =~ (rhel|centos|fedora) ]]; then
dnf install -y gcc make curl unzip pkgconf-pkg-config xtables-addons \
kernel-devel-$(uname -r) perl-Text-CSV_XS perl-MooseX-Types-NetAddr-IP jq
elif [[ "$ID" == "arch" ]]; then
pacman -Sy --noconfirm base-devel curl unzip pkgconf xtables-addons linux-headers jq
else
echo "Unsupported distro"; exit 1
fi
# -------------------------------
# Switch iptables backend to legacy
# -------------------------------
echo -e "\033[32m[2/7] Ensuring iptables legacy backend...\033[0m"
if command -v update-alternatives &>/dev/null; then
update-alternatives --set iptables /usr/sbin/iptables-legacy || true
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy || true
fi
iptables --version
# -------------------------------
# Build xtables-addons + xt_geoip kernel module
# -------------------------------
echo -e "\033[32m[3/7] Building xtables-addons...\033[0m"
cd /tmp
rm -rf xtables-addons-${XTA_VERSION}*
curl -LO https://inai.de/files/xtables-addons/xtables-addons-${XTA_VERSION}.tar.xz
tar xf xtables-addons-${XTA_VERSION}.tar.xz
cd xtables-addons-${XTA_VERSION}
./configure
make
make install
depmod -a
modprobe xt_geoip
echo -e "\033[32m✓ xt_geoip kernel module loaded.\033[0m"
# -------------------------------
# Check for GeoIP database files
# -------------------------------
echo -e "\033[32m[4/7] Verifying GeoIP binary database...\033[0m"
if [[ ! -d "$GEOIP_DIR" ]] || [[ -z "$(ls -A $GEOIP_DIR/*.iv4 2>/dev/null)" ]]; then
echo -e "\033[31mNo GeoIP .iv4 files found in $GEOIP_DIR\033[0m"
echo "Place binary files (.iv4, .iv6) manually before continuing."
exit 1
fi
echo "GeoIP database detected at $GEOIP_DIR."
# -------------------------------
# Build standalone xt_geoip_query (C program)
# -------------------------------
echo -e "\033[32m[5/7] Compiling working xt_geoip_query tool...\033[0m"
cat >/tmp/xt_geoip_query.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdint.h>
/*
* Working xt_geoip_query for xtables-addons ≥3.20
* Reads current binary database layout accurately.
*/
struct xt_geoip_entry {
uint32_t start;
uint32_t end;
} __attribute__((packed));
static void usage(void) {
fprintf(stderr, "Usage: xt_geoip_query <IPv4-address>\n");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
FILE *f;
struct in_addr addr;
struct xt_geoip_entry ent;
char path[512], code[3] = {0};
unsigned int i, found = 0;
const char *dir;
if (argc != 2)
usage();
if (!inet_aton(argv[1], &addr))
usage();
dir = getenv("XT_GEOIP_DATADIR");
if (!dir)
dir = "/usr/share/xt_geoip";
for (i = 0; i < 26 * 26; ++i) {
snprintf(code, sizeof(code), "%c%c", 'A' + i / 26, 'A' + i % 26);
snprintf(path, sizeof(path), "%s/%s.iv4", dir, code);
f = fopen(path, "rb");
if (!f)
continue;
while (fread(&ent, sizeof ent, 1, f) == 1) {
uint32_t ip = ntohl(addr.s_addr);
uint32_t start = ntohl(ent.start);
uint32_t end = ntohl(ent.end);
if (ip >= start && ip <= end) {
printf("%s\n", code);
found = 1;
break;
}
}
fclose(f);
if (found)
break;
}
if (!found)
printf("??\n");
return 0;
}
EOF
gcc -O2 -Wall /tmp/xt_geoip_query.c -o /usr/local/bin/xt_geoip_query
chmod 755 /usr/local/bin/xt_geoip_query
echo -e "\033[32m✓ xt_geoip_query built successfully.\033[0m"
# -------------------------------
# Test that binary lookup works
# -------------------------------
echo -e "\033[32m[6/7] Testing GeoIP lookup...\033[0m"
echo "If successful, this should display: US"
XT_GEOIP_DATADIR=$GEOIP_DIR /usr/local/bin/xt_geoip_query 8.8.8.8 || true
# -------------------------------
# Print example firewall setup (not applied)
# -------------------------------
echo -e "\033[32m[7/7] Installation complete.\033[0m\n"
echo "---------------------------------------------------------"
echo "GeoIP module and query tool installed successfully."
echo ""
echo "No firewall rules have been changed automatically."
echo ""
echo "Example safe rules to apply manually (US only, local allowed):"
echo ""
echo " iptables -F"
echo " iptables -I INPUT -i lo -j ACCEPT"
echo " iptables -I INPUT -s 10.0.0.0/8 -j ACCEPT"
echo " iptables -I INPUT -s 172.16.0.0/12 -j ACCEPT"
echo " iptables -I INPUT -s 192.168.0.0/16 -j ACCEPT"
echo " iptables -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT"
echo " iptables -I INPUT -m geoip --src-cc US -j ACCEPT"
echo " iptables -P INPUT DROP"
echo ""
echo "---------------------------------------------------------"
@vinookie
Copy link

vinookie commented Dec 9, 2024

No such file or directory
iptables v1.8.7 (nf_tables): Could not read geoip database
root@vmd155394:/usr/share/xt_geoip#

@asheroto
Copy link
Author

@vinookie What flavor and version of Linux are you running?

@EricDuminil
Copy link

EricDuminil commented Dec 14, 2024

Thanks, it looks promising.

You probably should set -e to stop the script if any step goes wrong.
libxtables-dev was missing on my Linux Mint 22 laptop.
The script will probably fail when not run as root.

Line 74 fails. There's no "/usr/lib/xtables-addons/" folder.

On which system was it tested?

@asheroto
Copy link
Author

I actually tested it on a Debian 10 Buster system and it worked perfectly, although yes, I did run it as root. Glad you reminded me! I'll adjust it.

@vinookie
Copy link

@vinookie What flavor and version of Linux are you running?

I had to move some files and then it worked out! Thanks for your response

@asheroto
Copy link
Author

Awesome! What files did you move?

@Rush-er
Copy link

Rush-er commented Jan 9, 2025

There is an issue with default location for /usr/lib/xtables-addons/xt_geoip_build

To fix it you need this part:

install-geoip.sh

Thanks for the script

@asheroto
Copy link
Author

Hi @Rush-er, thanks for the contribution!

Looks good, I'll update the script.

@GlassFeet99
Copy link

Thank you, had to made one revision - line 9 append "libnet-cidr-lite-perl"
Then worked perfectly thanks

@asheroto
Copy link
Author

asheroto commented Feb 7, 2025

@GlassFeet99 what Linux distro are you using?

@GlassFeet99
Copy link

@GlassFeet99 what Linux distro are you using?
Ubuntu 20.04.03. EOL coming my way, I know.

@weilhr
Copy link

weilhr commented Feb 21, 2025

@asheroto nice script :-) - thank you for putting it together.

I was using the script on a Debian 11.11 "Bullseye". In my case it was required to install libxtables-dev and libnet-cidr-lite-perl (as @GlassFeet99 already mentioned).

For those who do just copy/paste, be careful when running the examples. When using SSH it may block you from accessing the system if no other rule exists which allows your "good" SSH traffic ;-)

@lastsamurai26
Copy link

lastsamurai26 commented Mar 18, 2025

Maybe someone have a solution ( Debian 12)

i installed all Requirements but if i try to add the mod
depmod
modprobe xt_geoip
modprobe: FATAL: Module xt_geoip not found in directory /lib/modules/6.1.0-31-amd64

what did i missied ?

i found it myself
i need to install xtables-addons-dkms aswell :-) now it works but not after restart =(

@dcihon
Copy link

dcihon commented Mar 20, 2025

Do you know what changes I would need if I am running Arch Linux?

@asheroto
Copy link
Author

asheroto commented Oct 7, 2025

Hey all,

I've updated the script to work with multiple distros. I've also added libxtables-dev and libnet-cidr-lite-perl if needed.

I've confirmed it's working on Debian 12 Bookworm. Anyone else want to try their distro now? Probably about time to update the Geo-IP database anyway. 😁

There were several major changes. Please re-read the README before running to make sure this still works with your use case.

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment