Nginx GeoIP Module: Blocking Countries and Redirecting Traffic
Learn how to use the Nginx GeoIP module for location-based access control. See how to block specific countries or redirect users based on their IP address.
We recently had to restrict a client's dashboard to specific regions after detecting a surge in automated scrapers from a few unexpected countries. Instead of handling this at the application layer—which can be resource-intensive—we pushed the logic down to the web server. Using the Nginx GeoIP module is usually the most efficient way to handle this, though it does require a bit of setup.
Getting Started with GeoIP2
Before you can block a country, you need the actual database. Nginx doesn't come with built-in geo-location data. You’ll need the MaxMind GeoIP2 database. I recommend installing the libmaxminddb library and the Nginx module. On Debian or Ubuntu, it’s usually libnginx-mod-http-geoip2.
Once installed, you need to define the database path in your nginx.conf file.
NGINX# Inside the http block geoip2 /usr/share/maxmind/GeoLite2-Country.mmdb { $geoip2_data_country_code country iso_code; } map $geoip2_data_country_code $allowed_country { default no; US yes; CA yes; GB yes; }
This setup maps the country code to a variable $allowed_country. If the user isn't from the US, Canada, or the UK, the value is no.
Implementing Nginx Access Control
With the map directive in place, you can now enforce your rules. I prefer using a simple if block inside the location directive to handle the denial. It’s clean and easy to audit during an on-call shift.
NGINXlocation / { if ($allowed_country = no) { return 403; } # rest of your config }
If you need to perform a location-based redirect instead of a hard block, the logic remains similar to how I approach Mastering Nginx Rewrite Rules for SEO-Friendly URL Migrations. You simply replace the return 403 with a return 302 to a localized version of your site.
Dealing with Edge Cases
In my experience, you should always account for proxies. If your server sits behind Cloudflare or an AWS Load Balancer, Nginx will see the IP of the proxy, not the user. You’ll need to trust the X-Forwarded-For header.
NGINXset_real_ip_from 10.0.0.0/8; # Your proxy range real_ip_header X-Forwarded-For;
If you're still struggling with the underlying infrastructure, I often help teams with VPS Server Setup, Deployment & Hardening to ensure these headers and security modules are configured correctly from day one.
Comparison: Blocking Methods
When deciding how to handle traffic, consider where you want the logic to live.
| Method | Pro | Con |
|---|---|---|
| Nginx GeoIP | Very fast, low overhead | Requires module/DB maintenance |
| Cloudflare WAF | Offloads traffic entirely | Costs money for granular rules |
| App Middleware | Highly flexible | Slows down your application |
What I’d Do Differently
I initially tried to hardcode IP ranges in the Nginx config, which was a nightmare. Keeping track of CIDR blocks manually for an entire country is impossible. Using the GeoIP2 configuration makes the rules dynamic; when MaxMind updates their database, your Nginx server automatically starts using the new data without needing a config reload.
One catch: make sure your database files are readable by the www-data user. I've spent about two hours debugging a 403 Forbidden error that turned out to be a simple permission issue on the .mmdb file.
FAQ
Does the Nginx GeoIP module affect performance? It adds a negligible amount of latency—usually under 1ms per request—because it performs a memory lookup against the loaded database. It's much faster than hitting an external API.
How often should I update the GeoIP database? MaxMind updates their databases monthly. I usually set up a simple cron job to download the latest file and reload Nginx.
Can I block by city instead of country?
Yes, but you need the GeoLite2-City database and must update your geoip2 block to extract the city name or ID.
Ultimately, using the Nginx GeoIP module is a robust way to manage traffic. It’s not a replacement for a full WAF, but it’s a solid first line of defense for location-based access control. Just remember to keep your database files fresh and always test your configuration with nginx -t before reloading.