KBeezie

There's no place like ::1

Menu
  • Home
  • Start Here
  • Security Series
  • About

Using GeoIP in Nginx

2026/05/11 in Nginx

The ngx_http_geoip_module lets Nginx determine a visitor's country — and optionally city — based on their IP address. This can be used for country-level routing, language selection, passing location data to backend applications, or access control. Unlike the early days of Nginx, the GeoIP module is included by default in most packaged builds, so there's no recompilation required.

Is the module already available?

Check with:

nginx -V 2>&1 | tr ' ' '\n' | grep geoip

If you see --with-http_geoip_module in the output, you're set. If not, you may need to install the full Nginx package (some distros ship a "light" build that omits it):

# Debian/Ubuntu
apt install nginx-full

# RHEL/CentOS/Rocky — the default nginx package usually includes it
dnf install nginx

Install the GeoIP library

The module depends on the system-level GeoIP library:

# Debian/Ubuntu
apt install libgeoip-dev

# RHEL/CentOS/Rocky
dnf install GeoIP-devel

# macOS (Homebrew)
brew install geoip

Downloading the GeoIP database

MaxMind discontinued free public downloads of their GeoLite databases in 2018–2019, but the legacy .dat format is still maintained by the community at mailfud.org/geoip-legacy/. Grab the databases manually:

mkdir -p /usr/share/GeoIP
cd /usr/share/GeoIP
wget https://mailfud.org/geoip-legacy/GeoIP.dat.gz
wget https://mailfud.org/geoip-legacy/GeoIPv6.dat.gz
gunzip GeoIP.dat.gz GeoIPv6.dat.gz

To keep them up to date automatically, drop their update script into a weekly cron job:

wget https://mailfud.org/geoip-legacy/geoip_update.sh \
  -O /etc/cron.weekly/geoip_update
chmod +x /etc/cron.weekly/geoip_update

By default the script downloads to /usr/share/GeoIP and pulls the country, city, ASN, and ISP variants. Edit the DBDIR and FILES variables at the top of the script if you're using a different path or only need specific databases.

Configuring Nginx

Point Nginx at the database files inside the http { } block of your nginx.conf:

geoip_country /usr/share/GeoIP/GeoIP.dat;
geoip_country /usr/share/GeoIP/GeoIPv6.dat;
#geoip_city /usr/share/GeoIP/GeoIPCity.dat;      # optional
#geoip_city /usr/share/GeoIP/GeoIPCityv6.dat;    # optional

Reload Nginx and the following variables become available in any server block:

Variable Example
$geoip_country_code DE
$geoip_country_code3 DEU
$geoip_country_name Germany
$geoip_city_country_code DE (requires city DB)
$geoip_city Berlin (requires city DB)
$geoip_latitude / $geoip_longitude 52.52, 13.40

Full reference: nginx.org GeoIP module docs.

Country-based routing in Nginx

Serve different pages depending on where the visitor is:

server {
    server_name mysite.com www.mysite.com;
    root /opt/html/mysite.com;

    location / {
        try_files /index_$geoip_country_code.html index.html;
    }
}

A visitor from France hits index_FR.html if it exists, falling back to index.html. This is handy for language-specific landing pages without touching your backend code.

Blocking or allowing by country

Nginx's map directive combined with GeoIP makes for clean allow/deny rules:

map $geoip_country_code $allowed_country {
    default yes;
    CN no;   # block China
    RU no;   # block Russia
    KP no;   # block North Korea
}

server {
    location /admin {
        if ($allowed_country = no) {
            return 403;
        }
    }
}

Passing GeoIP values to PHP

If you want your backend application to know the visitor's location without doing its own lookup, pass the variables as FastCGI parameters:

fastcgi_param GEOIP_COUNTRY_CODE $geoip_country_code;
fastcgi_param GEOIP_COUNTRY_NAME $geoip_country_name;

Then in PHP:

$country = $_SERVER['GEOIP_COUNTRY_CODE'] ?? null;

if ($country === 'DE') {
    // German visitor — show EU-specific pricing or GDPR notice
}

Rate limiting by geography

Combine GeoIP with Nginx's rate-limiting for geo-aware throttling:

map $geoip_country_code $rate_limit_zone {
    default   "default";
    US        "us_only";
    CA        "us_only";
}

limit_req_zone $rate_limit_zone zone=default:10m rate=10r/s;
limit_req_zone $rate_limit_zone zone=us_only:10m rate=1r/s;

server {
    location /login {
        limit_req zone=$rate_limit_zone burst=5;
        # ...
    }
}

A note on geo-redirects

Country-based routing is useful for serving language-specific pages, regional pricing, or location-relevant content to your visitors based on their geography. Keep in mind that geo-IP lookups are not perfectly accurate — always provide a way for visitors to override the default (a language picker, for example).

Technical Audit Summary

This guide is maintained as part of a modular, SSL-first framework. Each configuration is audited for production stability and modern security standards.

Last Audit: May 2026
Environment: Debian Trixie (13)
Nginx: 1.30.0
PHP-FPM: 8.5.5

Compatibility: Tested against current stable releases. While optimized for the stack above, core logic remains relevant for Nginx 1.26+ and PHP 8.2+ environments.

Tags: nginx, geoip, geoip2, country, routing
©2026 KBeezie | Disclaimer | Privacy Notice