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).
This guide is maintained as part of a modular, SSL-first framework. Each configuration is audited for production stability and modern security standards.
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.