Introduction
Geo-blocking is a Web Application Firewall (WAF) feature that restricts access based on the geographic location of IP addresses. It's used to comply with regulations, reduce attack surface, or block traffic from high-risk regions. When geo-blocking fails, traffic from blocked countries reaches the application, potentially violating compliance requirements or exposing services to attacks. Geo-blocking issues typically stem from outdated GeoIP databases, incorrect country code specifications, rule ordering problems, IP header spoofing, or the GeoIP module not being enabled. Understanding how to configure and debug geo-blocking rules is essential for effective geographic access control.
Symptoms
When WAF geo-blocking is not filtering correctly, you will observe:
- Traffic from blocked countries still reaching the application
- Access logs showing requests from blocked country codes
- No block entries in WAF logs for specified countries
- Geo-blocking rules show as active but have no matches
- False positives - legitimate traffic being blocked incorrectly
- Inconsistent behavior - some IPs blocked, others from same country not
Testing from blocked country shows:
``bash
# From a blocked country (e.g., via VPN)
curl -v https://yourdomain.com/
# Expected: 403 Forbidden
# Actual: 200 OK (site loads normally)
WAF dashboard shows:
``
Geo-blocking Rule: Block Country X
Status: Enabled
Hits: 0
Access logs show requests from blocked countries:
``
192.0.2.1 (Country: CN) - GET /admin - 200
198.51.100.1 (Country: RU) - POST /api - 200
Common Causes
- 1.GeoIP database outdated - IP-to-country mapping stale or missing
- 2.Incorrect country codes - Using wrong ISO codes (CN vs CHN)
- 3.Rule not in correct position - Other rules allow before geo-block
- 4.GeoIP module disabled - MaxMind or GeoIP module not loaded
- 5.Missing database license - MaxMind requires license key for updates
- 6.IP header spoofing - X-Forwarded-For overriding real IP
- 7.Proxy/CDN hiding origin - CloudFlare, CloudFront hiding real IP
- 8.Rule exceptions misconfigured - Exception paths bypass geo-block
- 9.IPv6 not configured - Only IPv4 addresses blocked
- 10.Cache bypassing WAF - Cached responses served before WAF evaluation
Step-by-Step Fix
Step 1: Verify GeoIP Database Status
Check if GeoIP database is installed and current:
For ModSecurity with GeoIP: ```bash # Check GeoIP database location ls -la /usr/share/GeoIP/ ls -la /var/lib/GeoIP/
# Check database age stat /usr/share/GeoIP/GeoIP.dat stat /usr/share/GeoIP/GeoIPv6.dat
# Check MaxMind database ls -la /usr/share/GeoIP/GeoLite2-Country.mmdb
# Check ModSecurity GeoIP configuration grep -r "SecGeoLookupDb" /etc/modsecurity/ ```
For Nginx with GeoIP: ```bash # Check Nginx GeoIP module nginx -V 2>&1 | grep -i geoip
# Check GeoIP database in Nginx config grep -r "geoip_country|geoip_city" /etc/nginx/
# Test GeoIP lookup nginx -t 2>&1 ```
For AWS WAF: ```bash # Check AWS WAF geo-match rules aws wafv2 list-rules --scope REGIONAL --region us-east-1
# Get rule details aws wafv2 get-rule --scope REGIONAL \ --id <rule-id> \ --name <rule-name> ```
Step 2: Update GeoIP Database
Ensure GeoIP database is current:
MaxMind GeoLite2 (free, requires account): ```bash # Install GeoIP update tool sudo apt install geoipupdate
# Configure with license key sudo vim /etc/GeoIP.conf ```
Add configuration:
``ini
AccountID YOUR_ACCOUNT_ID
LicenseKey YOUR_LICENSE_KEY
EditionIDs GeoLite2-Country GeoLite2-City
DatabaseDirectory /usr/share/GeoIP
Run update: ```bash # Update databases sudo geoipupdate
# Verify update ls -la /usr/share/GeoIP/GeoLite2-Country.mmdb
# Set up automatic updates sudo systemctl enable geoipupdate.timer ```
Manual database update: ```bash # Download latest GeoLite2 wget https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_KEY -O GeoLite2-Country.tar.gz
# Extract and install tar -xzf GeoLite2-Country.tar.gz sudo mv GeoLite2-Country_*/GeoLite2-Country.mmdb /usr/share/GeoIP/ ```
Step 3: Test GeoIP Lookup
Verify GeoIP lookups work correctly:
```bash # Test IP-to-country lookup # Using mmdblookup (MaxMind) mmdblookup --file /usr/share/GeoIP/GeoLite2-Country.mmdb --ip 8.8.8.8
# Using geoiplookup (legacy) geoiplookup 8.8.8.8
# Test specific country IPs # China IP geoiplookup 202.96.128.86
# Russia IP geoiplookup 77.88.55.55
# US IP geoiplookup 8.8.8.8 ```
Expected output for US IP:
``
GeoIP Country Edition: US, United States
Step 4: Configure Geo-Blocking Rules Correctly
Set up proper geo-blocking rules:
ModSecurity Configuration:
``bash
sudo vim /etc/modsecurity/modsecurity.conf
Add GeoIP configuration: ```apache # Load GeoIP database SecGeoLookupDb /usr/share/GeoIP/GeoLite2-Country.mmdb
# Geo-blocking rule SecRule REMOTE_ADDR "@geoLookup" \ "phase:1,chain,deny,status:403,id:100001,msg:'Blocked by GeoIP'" SecRule GEO:COUNTRY_CODE "@pm CN RU KP IR" \ "msg:'Blocked country: %{GEO:COUNTRY_CODE}'" ```
Nginx Configuration: ```nginx # In http block geoip_country /usr/share/GeoIP/GeoLite2-Country.mmdb;
# Map for blocked countries map $geoip_country_code $blocked_country { default 0; CN 1; RU 1; KP 1; IR 1; }
# In server block server { if ($blocked_country) { return 403 "Access denied from your country"; } } ```
AWS WAF Configuration:
``bash
# Create geo-match rule
aws wafv2 create-rule-group \
--name GeoBlockRuleGroup \
--scope REGIONAL \
--capacity 10 \
--rules '[{
"Name": "BlockCountries",
"Priority": 0,
"Statement": {
"GeoMatchStatement": {
"CountryCodes": ["CN", "RU", "KP", "IR"]
}
},
"Action": { "Block": {} },
"VisibilityConfig": {
"SampledRequestsEnabled": true,
"CloudWatchMetricsEnabled": true,
"MetricName": "GeoBlockMetric"
}
}]'
Step 5: Use Correct Country Codes
Ensure correct ISO 3166-1 alpha-2 codes:
```bash # Common country codes reference cat << 'EOF' # Use these ISO 3166-1 alpha-2 codes: CN - China RU - Russia KP - North Korea IR - Iran NG - Nigeria BR - Brazil IN - India US - United States GB - United Kingdom DE - Germany JP - Japan KR - South Korea (NOT KP!) AU - Australia CA - Canada
# WRONG codes that won't work: CHN - China (use CN, not CHN) RUS - Russia (use RU, not RUS) KOR - South Korea (use KR, not KOR) EOF ```
Verify in configuration:
``bash
# Check your geo-block configuration
grep -E "Country|COUNTRY|geo" /etc/modsecurity/*.conf
grep -E "pm\(|@pm" /etc/modsecurity/*.conf
Step 6: Handle Proxy/CDN IP Addresses
When behind CDN or proxy, get real client IP:
CloudFlare Configuration: ```nginx # In Nginx set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 108.162.192.0/18; set_real_ip_from 131.0.72.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 162.158.0.0/15; set_real_ip_from 172.64.0.0/13; set_real_ip_from 173.245.48.0/20; set_real_ip_from 188.114.96.0/15; set_real_ip_from 190.93.240.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17;
real_ip_header CF-Connecting-IP; real_ip_recursive on; ```
AWS CloudFront Configuration: ```nginx # Get CloudFront IP ranges curl -s https://ip-ranges.amazonaws.com/ip-ranges.json | jq -r '.prefixes[] | select(.service=="CLOUDFRONT") | .ip_prefix'
# Configure in Nginx set_real_ip_from 13.32.0.0/15; # ... add all CloudFront ranges real_ip_header X-Forwarded-For; real_ip_recursive on; ```
ModSecurity with proxy:
``apache
# Use correct IP header
SecRule REQUEST_HEADERS:X-Forwarded-For ".*" \
"phase:1,pass,nolog,chain,id:100000"
SecRule REMOTE_ADDR "@geoLookup"
Step 7: Verify Rule Order and Priority
Ensure geo-block rule executes before allow rules:
```bash # Check rule order in ModSecurity grep -n "SecRule" /etc/modsecurity/*.conf | head -20
# Rules execute in phase and ID order # Geo-block should be in phase:1 or phase:2 ```
Correct rule ordering: ```apache # GOOD: Geo-block first, then other checks SecRule REMOTE_ADDR "@geoLookup" "phase:1,chain,deny,id:100001" SecRule GEO:COUNTRY_CODE "@pm CN RU" "msg:'Geo-blocked'"
# Then other rules SecRule REQUEST_URI "/admin" "phase:2,allow,id:100002" # This won't execute for blocked countries
# BAD: Allow rule before geo-block SecRule REQUEST_URI "/public" "phase:1,allow,id:100001" # Allows all, geo-block never runs SecRule REMOTE_ADDR "@geoLookup" "phase:2,chain,deny,id:100002" # Too late! ```
Step 8: Test Geo-Blocking
Verify geo-blocking is working:
```bash # Test with IPs from different countries # Use VPN or proxy to test from blocked countries
# Test from China IP curl -H "X-Forwarded-For: 202.96.128.86" https://yourdomain.com/test # Expected: 403 Forbidden
# Test from US IP curl -H "X-Forwarded-For: 8.8.8.8" https://yourdomain.com/test # Expected: 200 OK
# Check WAF logs tail -f /var/log/modsecurity/audit.log | grep -i "geo|country" ```
Using online testing tools: ```bash # Use services like: # - https://geo.ipify.org/ # - https://iplocation.com/ # - VPN services to test from different countries
# Test via curl with known IPs declare -A country_ips=( ["CN"]="202.96.128.86" ["RU"]="77.88.55.55" ["US"]="8.8.8.8" ["GB"]="8.8.4.4" )
for country in "${!country_ips[@]}"; do echo "Testing $country (${country_ips[$country]}):" curl -s -o /dev/null -w "%{http_code}" \ -H "X-Forwarded-For: ${country_ips[$country]}" \ https://yourdomain.com/ echo "" done ```
Verification
After configuration, verify geo-blocking works:
```bash # Check WAF rule is loaded # ModSecurity: curl -s http://localhost/modsec-status | grep geo
# Check blocked requests in logs grep -r "Blocked country|GeoIP" /var/log/modsecurity/audit.log
# Verify GeoIP module is loaded nginx -V 2>&1 | grep geoip apachectl -M | grep geoip
# Test from blocked country (via VPN) curl -v https://yourdomain.com/ 2>&1 | grep -E "HTTP|403" # Expected: HTTP/1.1 403 Forbidden ```
Monitor geo-blocking effectiveness: ```bash # Count blocked requests by country grep "Blocked country" /var/log/modsecurity/audit.log | \ awk -F'country: ' '{print $2}' | tr -d "'" | sort | uniq -c | sort -rn
# Check recent blocks grep "Blocked country" /var/log/modsecurity/audit.log | tail -20 ```
Prevention
To prevent geo-blocking issues:
- 1.Set up automatic database updates:
- 2.```bash
- 3.# Cron job for GeoIP updates
- 4.0 3 * * 0 /usr/bin/geoipupdate > /var/log/geoip-update.log 2>&1
- 5.
` - 6.Monitor database age:
- 7.```bash
- 8.# Alert if database older than 30 days
- 9.DB_AGE=$(($(date +%s) - $(stat -c %Y /usr/share/GeoIP/GeoLite2-Country.mmdb)))
- 10.if [ $DB_AGE -gt 2592000 ]; then
- 11.echo "GeoIP database is older than 30 days"
- 12.fi
- 13.
` - 14.Log geo-blocking decisions:
- 15.```apache
- 16.SecRule GEO:COUNTRY_CODE "@pm CN RU" \
- 17."phase:1,deny,log,auditlog,msg:'Geo-block: %{GEO:COUNTRY_CODE}'"
- 18.
` - 19.Use both IPv4 and IPv6 databases:
- 20.```apache
- 21.SecGeoLookupDb /usr/share/GeoIP/GeoLite2-Country.mmdb
- 22.# This single database handles both IPv4 and IPv6
- 23.
` - 24.Test rules before production:
- 25.```bash
- 26.# Test configuration
- 27.mlogc /etc/modsecurity/modsecurity.conf
- 28.# Or use ModSecurity audit mode first
- 29.SecRuleEngine DetectionOnly
- 30.
` - 31.Document blocked countries:
- 32.```bash
- 33.# Create documentation
- 34.cat > /etc/modsecurity/GEO-BLOCK-README << 'EOF'
- 35.Blocked Countries: CN, RU, KP, IR
- 36.Reason: Compliance and security policy
- 37.Last Updated: 2024-12-12
- 38.Next Review: 2025-03-12
- 39.EOF
- 40.
` - 41.Handle exceptions properly:
- 42.```apache
- 43.# Exception for specific paths or IPs
- 44.SecRule REQUEST_URI "^/api/webhook" "phase:1,allow,id:99999"
- 45.
` - 46.Regular testing schedule:
- 47.```bash
- 48.# Weekly geo-block test script
- 49.#!/bin/bash
- 50.BLOCKED_IPS="202.96.128.86 77.88.55.55"
- 51.for ip in $BLOCKED_IPS; do
- 52.status=$(curl -s -o /dev/null -w "%{http_code}" -H "X-Forwarded-For: $ip" https://yourdomain.com/)
- 53.if [ "$status" != "403" ]; then
- 54.echo "ALERT: IP $ip not blocked (status: $status)"
- 55.fi
- 56.done
- 57.
`
Related Articles
- [Technical troubleshooting: Fix Certificate Chain Incomplete SSL Validation Is](certificate-chain-incomplete-ssl-validation)
- [Fix Ddos Attack Mitigation Waf Rate Limiting Issue in Network Security](ddos-attack-mitigation-waf-rate-limiting)
- [Fix DNS Hijacking Spoofing Attack Issue in Network Security](dns-hijacking-spoofing-attack)
- [Fix firewall iptables rules not persisting across reboot Issue in Network-Security](firewall-iptables-rules-not-persisting-across-reboot)
- [Fix firewall rule blocking legitimate traffic Issue in Network-Security](firewall-rule-blocking-legitimate-traffic)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "WAF Geoblock Not Filtering", "description": "Fix WAF geo-blocking not working. Configure GeoIP rules, update databases, and verify country-based access control.", "url": "https://www.fixwikihub.com/fix-security-waf-geoblock-not-filtering", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2025-12-12T17:36:23.752Z", "dateModified": "2025-12-12T17:36:23.752Z" } </script>