Country-based rules can cut a large share of brute-force and spam traffic, or restrict an admin area to the countries where your team works. Nginx does this with the GeoIP2 module and a MaxMind country database. This guide sets up geo blocking in Nginx carefully, so you don't block customers by accident.
Before you start
- Nginx on a VPS with root access.
- A free MaxMind account to download GeoLite2 databases (requires accepting their licence), or a commercial GeoIP2 subscription.
- If Nginx sits behind Cloudflare, use Cloudflare's country header (
CF-IPCountry) or WAF rules instead; Nginx only sees Cloudflare's IPs otherwise. - Know the limits: VPNs and proxies bypass country rules, and geolocation databases are not 100% accurate.
Install the GeoIP2 module
- Ubuntu/Debian:
sudo apt install libnginx-mod-http-geoip2 - Other systems: build
ngx_http_geoip2_moduleas a dynamic module for your Nginx version, withlibmaxminddbinstalled.
The older geoip module uses discontinued legacy databases; use GeoIP2.
Download and update the database
- Install the updater:
sudo apt install geoipupdate. - Put your account ID and licence key in
/etc/GeoIP.confwithEditionIDs GeoLite2-Country. - Run
sudo geoipupdate. The database lands in/usr/share/GeoIP/(or/var/lib/GeoIP/). - Schedule weekly updates, e.g. with a cron job or systemd timer that runs
geoipupdateand thensystemctl reload nginx.
Map countries in the http context
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
auto_reload 24h;
$geoip2_country_code country iso_code;
}
map $geoip2_country_code $blocked_country {
default 0;
XX 1;
YY 1;
}Replace XX/YY with ISO 3166 two-letter codes. On Debian, load_module is handled by the package; otherwise add load_module modules/ngx_http_geoip2_module.so; at the top of nginx.conf.
Block listed countries site-wide
server {
if ($blocked_country) { return 403; }
# rest of the server block
}Allow only some countries (allow list)
map $geoip2_country_code $allowed_country {
default 0;
DE 1;
US 1;
SG 1;
}Then if ($allowed_country = 0) { return 403; }. Allow lists are stricter and block travellers and VPN users; use them for internal tools rather than public sites.
Restrict only sensitive areas (recommended)
Blocking entire countries from a public site can lose customers and search traffic. Applying the rule only to login and admin URLs keeps the security benefit with less risk:
location ~ ^/(wp-login\.php|wp-admin|admin) {
if ($allowed_country = 0) { return 403; }
# the usual PHP handling for these paths
}Exclude your own IPs and good bots
Combine with a geo block so your office or monitoring IPs always pass, regardless of country:
geo $trusted_ip { default 0; 203.0.113.10 1; }
map "$trusted_ip:$blocked_country" $deny_request { default 0; "0:1" 1; }Then use if ($deny_request) { return 403; }.
Log the country
Add $geoip2_country_code to your log format to see where traffic and attacks come from before deciding what to block (access log formats).
Test
sudo nginx -t && sudo systemctl reload nginx- Look up an IP's country:
mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 8.8.8.8 country iso_code - Test from a server or VPN endpoint in a blocked country and confirm a 403.
Troubleshooting
- Variable is always empty: wrong database path, or Nginx sees a proxy IP. Configure
real_ipfirst. - "unknown directive geoip2": module not loaded.
- Legitimate users blocked: database outdated or the user is on a VPN. Prefer protecting only admin paths.
FAQ: geo blocking in Nginx
Is geo blocking bad for SEO?
It can be if search engine crawlers come from a blocked country. Don't block countries crawlers use, or limit rules to admin paths.
Is a firewall better for this?
Firewalls like CSF can block countries at network level for all services; Nginx rules are finer-grained per URL.
Can I do this on shared hosting?
Use your CDN's country rules or a security plugin; server modules are managed by the host.
