Linux

Block non-US Network Connections with GeoIP, UFW and ipset

December 15, 2025 Rich 3 min read

I have nginx configured already to drop connections from non-US based IP addresses using the libnginx-mod-geoip module. Taking this a step further, I decided to block all non-US based IP addresses to this server at the firewall using ipset and UFW in combination with the ipdeny.com US zone file. Obviously this will not prevent VPN or proxied connections from getting here, but it will lighten the load on fail2ban and nginx, which is great.

UFW

/etc/ufw/rules.before

Beginning of the file, in *filter: section:

:ufw-geoip - [0:0]

End of the file, before COMMIT:

-A ufw-geoip -m set --match-set allowed_us src -j RETURN
-A ufw-geoip -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "GEOIP BLOCK: "
-A ufw-geoip -j DROP
-A ufw-before-input -j ufw-geoip

Quick way to analyze blocks:

dmesg | grep GEO | grep -o 'SRC=[^ ]*' | cut -d= -f2 | grep -v 0.0.0.0 | sort -rn | uniq -c | sort -rn

Update script

/usr/local/sbin/update-geoip-script.sh:

#!/usr/bin/env bash
set -euo pipefail

# Countries ALLOWED
COUNTRIES=("us")   # add "ca" etc here if needed COUNTRIES=("us" "ca" "ru")

IPSET_NAME="allowed_us"
TMP_DIR="$(mktemp -d)"
IPDENY_BASE="https://www.ipdeny.com/ipblocks/data/countries"

cleanup() {
    rm -rf "$TMP_DIR"
}
trap cleanup EXIT

# Make sure ipset exists (create if missing)
if ! ipset list "$IPSET_NAME" &>/dev/null; then
    ipset create "$IPSET_NAME" hash:net family inet hashsize 524288 maxelem 1000000
fi

# Build a new temp ipset so we can swap atomically
NEW_SET="${IPSET_NAME}_new"

# Delete leftover temp set if exists
if ipset list "$NEW_SET" &>/dev/null; then
    ipset destroy "$NEW_SET"
fi

ipset create "$NEW_SET" hash:net family inet hashsize 524288 maxelem 1000000

echo "Downloading country CIDR lists..."
for CC in "${COUNTRIES[@]}"; do
    ZONE_FILE="$TMP_DIR/${CC}.zone"
    curl -sSf "${IPDENY_BASE}/${CC}.zone" -o "$ZONE_FILE"
    while read -r net; do
        # skip blanks
        [[ -z "$net" ]] && continue
        ipset add "$NEW_SET" "$net"
    done < "$ZONE_FILE"
done

echo "Swapping ipset ${NEW_SET} -> ${IPSET_NAME}"
ipset swap "$NEW_SET" "$IPSET_NAME"
ipset destroy "$NEW_SET"

echo "Update complete for ${IPSET_NAME}"

nginx

Install GeoIP module:

apt install libnginx-mod-geoip

/etc/nginx/nginx.conf

Put this in the http block.

geoip_country /usr/share/GeoIP/GeoIP.dat;

/etc/nginx/snippets/geoip-block.conf

This blocks everything except US or Canadian based IP addresses based on the libnginx-mod-geoip module with an HTTP 403.

if ($geoip_country_code !~ "^(US|CA)$") {
    rewrite / /geoip-block;
}

location /geoip-block {
    default_type text/plain;
    return 403 '$geoip_country_name IPs are blocked here.';
}

Virtual host

In your virtual host configuration file, include the nginx configuration for GeoIP.

include /etc/nginx/snippets/geoip-block.conf;

Leave a comment