Security

Block or allow countries in Nginx with GeoIP2

Use the GeoIP2 module and a GeoLite2 country database to allow or block traffic by country in Nginx, restrict only admin areas, keep the database updated and avoid locking out real users.

AdvancedLinux VPS3 min read

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_module as a dynamic module for your Nginx version, with libmaxminddb installed.

The older geoip module uses discontinued legacy databases; use GeoIP2.

Download and update the database

  1. Install the updater: sudo apt install geoipupdate.
  2. Put your account ID and licence key in /etc/GeoIP.conf with EditionIDs GeoLite2-Country.
  3. Run sudo geoipupdate. The database lands in /usr/share/GeoIP/ (or /var/lib/GeoIP/).
  4. Schedule weekly updates, e.g. with a cron job or systemd timer that runs geoipupdate and then systemctl 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.

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

  1. sudo nginx -t && sudo systemctl reload nginx
  2. Look up an IP's country: mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 8.8.8.8 country iso_code
  3. 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_ip first.
  • "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.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.