GeoIP restriction in Nginx helps reduce unwanted traffic before requests reach your applications. When combined with ModSecurity, you get two complementary protection layers:
This approach is useful when you want strict geographic access control and stronger edge filtering.
On Ubuntu/Debian:
sudo apt update
sudo apt install -y libnginx-mod-http-geoip2
Place the GeoLite2 country database in a stable path:
sudo mkdir -p /usr/share/GeoIP
sudo cp GeoLite2-Country.mmdb /usr/share/GeoIP/GeoLite2-Country.mmdb
Define GeoIP extraction and a country policy map once in the http block of /etc/nginx/nginx.conf:
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
$geoip2_data_country_iso_code country iso_code;
}
map $geoip2_data_country_iso_code $allowed_country {
default yes;
RU no; # Russia
UA no; # Ukraine
CN no; # China
BY no; # Belarus
IL no; # Israel
CO no; # Colombia
LT no; # Lithuania
LV no; # Latvia
EE no; # Estonia
}
This is the global policy definition. You manage blocked countries in one place.
Use a shared snippet and include it in every active server block.
Create /etc/nginx/snippets/geoip-country-block.conf:
if ($allowed_country = no) {
return 444;
}
Then include it in each site server block:
server {
server_name Site1.mouradcloud.com;
include /etc/nginx/snippets/geoip-country-block.conf;
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;
location / {
proxy_pass http://site1_backend;
}
}
Why this is recommended:
nginx.conf,If you only run one or two sites, you can enforce directly in each server block:
server {
server_name Site2.mouradcloud.com;
if ($allowed_country = no) {
return 444;
}
location / {
proxy_pass http://site2_backend;
}
}
This works, but becomes harder to maintain as site count grows.
GeoIP and ModSecurity are different controls and should run together:
Typical directives:
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/modsecurity.conf;
sudo nginx -t
sudo systemctl reload nginx
Use this one-liner to aggregate IPs from both access and error logs:
sudo awk 'FILENAME ~ /access/ {ips[$1]++} FILENAME ~ /error/ {while (match($0,/([0-9]{1,3}\.){3}[0-9]{1,3}/)) {ip=substr($0,RSTART,RLENGTH); ips[ip]++; $0=substr($0,RSTART+RLENGTH)}} END {for (ip in ips) print ips[ip], ip}' /var/log/nginx/*access*.log /var/log/nginx/*error*.log | sort -nr
What it does:
sudo awk '$9 ~ /^[45][0-9][0-9]$/ {ips[$1]++} END {for (ip in ips) print ips[ip], ip}' /var/log/nginx/*access*.log | sort -nr
This helps quickly identify suspicious clients that generated failures.
For long-term operations, use global policy plus centralized snippet-based enforcement:
nginx.conf,This gives cleaner operations than repeating custom logic in each individual site file.