# Nginx GeoIP Not Working
GeoIP should block countries, route based on location, or show region-specific content. Instead, $geoip_country_code is empty, country blocks don't work, or all requests come from the same detected country. GeoIP functionality depends on module availability, database configuration, and correct usage.
Understanding Nginx GeoIP
- 1.Nginx GeoIP requires:
- 2.GeoIP module compiled into Nginx (
ngx_http_geoip_module) - 3.MaxMind GeoIP database files
- 4.
geoip_countryorgeoip_citydirective - 5.Variables like
$geoip_country_code
Check module availability:
``bash
nginx -V 2>&1 | grep -o geoip
# Should show: --with-http_geoip_module
If missing, you need to recompile Nginx with the module or install the package version that includes it.
Common Cause 1: Module Not Compiled/Installed
The GeoIP module isn't available in your Nginx build.
Diagnosis: ```bash # Check if module is compiled nginx -V 2>&1 | grep geoip
# Check installed modules (some distributions use dynamic modules) ls /etc/nginx/modules/ ls /usr/lib/nginx/modules/
# Check if module is loaded grep -r "load_module" /etc/nginx/nginx.conf ```
Solution:
For dynamic modules:
``nginx
# In nginx.conf main context
load_module modules/ngx_http_geoip_module.so;
For missing module: ```bash # Ubuntu/Debian - install extra modules sudo apt install libnginx-mod-http-geoip
# Or install full nginx sudo apt install nginx-full
# CentOS/RHEL sudo yum install nginx-module-geoip ```
For source compilation:
``bash
./configure --with-http_geoip_module
make
sudo make install
Common Cause 2: GeoIP Database Not Configured
The database path isn't set or the database file doesn't exist.
Problematic config:
``nginx
http {
geoip_country GeoIP.dat; # Where is this file?
}
Diagnosis: ```bash # Check if database exists ls -la /usr/share/GeoIP/
# Common locations ls -la /var/lib/GeoIP/ ls -la /usr/local/share/GeoIP/
# Check Nginx config for database path grep -r "geoip" /etc/nginx/ ```
Solution: Specify full path: ```nginx http { geoip_country /usr/share/GeoIP/GeoIP.dat;
# For city data geoip_city /usr/share/GeoIP/GeoLiteCity.dat; } ```
Common Cause 3: GeoIP Database Missing or Outdated
The database files don't exist or are too old.
Diagnosis: ```bash # Check database files ls -la /usr/share/GeoIP/
# Check database age stat /usr/share/GeoIP/GeoIP.dat
# Test database with geoiplookup geoiplookup 8.8.8.8 ```
Solution: Install GeoIP database: ```bash # Ubuntu/Debian sudo apt install geoip-bin geoip-database
# CentOS/RHEL sudo yum install geoip geoip-data
# Download from MaxMind (GeoLite2 requires free account) wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz tar xzf GeoLite2-Country.tar.gz sudo mv GeoLite2-Country.mmdb /usr/share/GeoIP/ ```
Note: GeoIP Legacy is deprecated. Use GeoIP2 for current data.
Common Cause 4: Using GeoIP2 with Legacy Module
The GeoIP module only supports legacy database format, not GeoIP2/MMDB.
Problem:
``nginx
# Legacy module
geoip_country /usr/share/GeoIP/GeoLite2-Country.mmdb;
# This won't work - .mmdb format requires different module
Solution: Use GeoIP2 module: ```bash # Install geoip2 module sudo apt install libnginx-mod-http-geoip2
# Or compile with geoip2 ./configure --add-dynamic-module=/path/to/ngx_http_geoip2_module ```
```nginx # Load module load_module modules/ngx_http_geoip2_module.so;
http { geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb { $geoip2_country_code country iso_code; $geoip2_country_name country names en; } } ```
Common Cause 5: GeoIP Variables Empty
Variables like $geoip_country_code return empty values.
Diagnosis: ```bash # Add header to see value curl -H "X-Forwarded-For: 8.8.8.8" http://localhost/test | grep Country
# Or in Nginx config add_header X-Country $geoip_country_code; ```
Common causes:
- 1.Database not loaded:
- 2.```bash
- 3.sudo nginx -t
- 4.# Should not show "geoip_country" errors
- 5.
` - 6.IP is private (not in database):
- 7.Private IPs (192.168.x.x, 10.x.x.x) have no country.
# Log to see detected IP
add_header X-Detected-IP $remote_addr;- 1.Behind proxy, real IP not detected:
- 2.```nginx
- 3.# Use real IP from header
- 4.set_real_ip_from 10.0.0.0/8;
- 5.real_ip_header X-Forwarded-For;
geoip_country /usr/share/GeoIP/GeoIP.dat; ```
- 1.Module variables changed:
- 2.GeoIP2 uses different variable names:
- 3.```nginx
- 4.geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
- 5.$geoip_country_code country iso_code; # Not $geoip_country_code
- 6.}
- 7.
`
Common Cause 6: Country Blocking Not Working
Country-based blocks don't apply.
Problematic config: ```nginx http { geoip_country GeoIP.dat;
# Block specific countries if ($geoip_country_code = CN) { return 403; } } ```
Problems:
- if directive issues in Nginx
- Variable might be empty
- Country code format (CN vs cn)
Solution: Use geo/map for blocking: ```nginx http { geoip_country /usr/share/GeoIP/GeoIP.dat;
# Define blocked countries geo $blocked_country { default 0; CN 1; RU 1; KP 1; }
map $geoip_country_code $block_request { default 0; CN 1; RU 1; KP 1; }
server { location / { if ($block_request) { return 403; }
add_header X-Country $geoip_country_code always; } } } ```
Better approach using map: ```nginx http { geoip_country /usr/share/GeoIP/GeoIP.dat;
map $geoip_country_code $allowed { default yes; CN no; RU no; }
server { location / { if ($allowed = no) { return 403 "Country blocked"; } } } } ```
Common Cause 7: GeoIP City Not Working
City-level detection returns empty values.
Diagnosis: ```nginx http { geoip_city /usr/share/GeoIP/GeoLiteCity.dat;
server { location /test { add_header X-City $geoip_city; add_header X-Region $geoip_region; add_header X-Country $geoip_city_country_code; return 200; } } } ```
curl -H "X-Forwarded-For: 8.8.8.8" -I http://localhost/test- 1.Causes:
- 2.Database file doesn't exist
- 3.Using country database instead of city database
- 4.IP doesn't have city-level resolution
- 5.GeoIP2 uses different variable mapping
GeoIP2 city configuration:
``nginx
geoip2 /usr/share/GeoIP/GeoLite2-City.mmdb {
$geoip2_city city names en;
$geoip2_region subdivisions 0 iso_code;
$geoip2_country country iso_code;
$geoip2_latitude location latitude;
$geoip2_longitude location longitude;
}
Common Cause 8: GeoIP Behind CDN/Proxy
All requests appear from same CDN IP.
Problem:
``nginx
geoip_country GeoIP.dat;
# $remote_addr is CDN IP, not user IP
Solution: Use real IP module: ```nginx # Set trusted proxies set_real_ip_from 103.21.244.0/22; # Cloudflare set_real_ip_from 103.22.200.0/22; set_real_ip_from 173.245.48.0/20; # Add all CDN/proxy IPs
real_ip_header CF-Connecting-IP; # Cloudflare # or real_ip_header X-Forwarded-For;
real_ip_recursive on;
geoip_country /usr/share/GeoIP/GeoIP.dat; ```
Now $remote_addr contains the user's real IP.
Verification Steps
- 1.Check module:
- 2.```bash
- 3.nginx -V 2>&1 | grep geoip
- 4.
` - 5.Check database:
- 6.```bash
- 7.ls -la /usr/share/GeoIP/
- 8.
` - 9.Test GeoIP lookup:
- 10.```nginx
- 11.location /geoip-test {
- 12.add_header X-Country $geoip_country_code;
- 13.return 200;
- 14.}
- 15.
`
```bash curl -H "X-Forwarded-For: 8.8.8.8" -I http://localhost/geoip-test # Should show X-Country: US
curl -H "X-Forwarded-For: 1.1.1.1" -I http://localhost/geoip-test # Should show X-Country: AU ```
- 1.Check Nginx configuration:
- 2.```bash
- 3.sudo nginx -t
- 4.
` - 5.Monitor logs:
- 6.```nginx
- 7.log_format geo '$remote_addr - $geoip_country_code [$time_local]';
- 8.access_log /var/log/nginx/geo.log geo;
- 9.
`
Complete Working Configuration
```nginx load_module modules/ngx_http_geoip2_module.so;
http { geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb { $geoip_country_code country iso_code; $geoip_country_name country names en; }
geoip2 /usr/share/GeoIP/GeoLite2-City.mmdb { $geoip_city city names en; $geoip_region subdivisions 0 names en; $geoip_latitude location latitude; $geoip_longitude location longitude; }
# Trust CDN/proxy IPs set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; set_real_ip_from 192.168.0.0/16; real_ip_header X-Forwarded-For; real_ip_recursive on;
# Country blocking map $geoip_country_code $blocked { default 0; CN 1; RU 1; }
# Geographic routing map $geoip_country_code $backend { default backend-eu; US backend-us; CA backend-us; GB backend-eu; DE backend-eu; }
upstream backend-eu { server eu.example.com; }
upstream backend-us { server us.example.com; }
server { location / { if ($blocked) { return 403 "Country blocked"; }
add_header X-Country $geoip_country_code always; proxy_pass http://$backend; } } } ```
Quick Reference
| Issue | Cause | Fix |
|---|---|---|
| Variables empty | Module not loaded | Install/load module |
| Variables empty | Database missing | Install GeoIP database |
| Wrong country | Behind proxy | Configure real_ip_header |
| City not detected | City database missing | Install city database |
| mmdb not working | Legacy module | Use geoip2 module |
| Block not working | Empty variable | Use map for blocking |
| All same country | Private IPs only | Test with public IP |
GeoIP requires module, database, and correct configuration chain. Start by verifying the module is loaded and database exists, then test with known public IPs.