Using GeoIP 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:

$geoip_country_code
Two-letter country code — DE, JP, US
$geoip_country_code3
Three-letter country code — DEU, JPN, USA
$geoip_country_name
Full country name — Germany, Japan, United States
$geoip_city
City name — requires city database
$geoip_city_country_code
Country code from the city database — alternate source, requires city DB
$geoip_latitude / $geoip_longitude
Approximate coordinates — 52.52, 13.40. Requires city database.

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 a historical reference. The configuration is correct and functional, though GeoIP is no longer active on this server — the current stack doesn't use location-based routing or access control.

Last Audit: May 2026
Environment: Debian Trixie (13)
Nginx: 1.30.2

Compatibility: The ngx_http_geoip_module is included in all standard Nginx builds. The community-maintained database at mailfud.org tracks the legacy MaxMind .dat format — for new deployments, consider the ngx_http_geoip2_module with MaxMind's current MMDB format instead.

2026-05-21: Historical audit — bumped Nginx to 1.30.1. The module was removed from the live configuration because no active features depended on it, and the per-request lookup overhead wasn't justified. Configuration remains correct for anyone who needs it. Added recommendation to consider the geoip2 module for new deployments using MaxMind's current MMDB format.