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;