Skip to main content

Access Control: IP Whitelist, Blacklist, and Country Blocking

IP-based access control at the web server level is one of the most fundamental and effective security measures. From restricting admin panels to office IPs, enforcing country-level service limits, and blocking malicious bots — this guide covers implementation with both Nginx and Apache.


Nginx IP-Based Access Control

allow / deny Directives

# Restrict access to a specific path
location /admin/ {
# Allowed IPs (evaluated in order)
allow 192.168.1.0/24; # Internal network
allow 203.0.113.10; # Office static IP
allow 127.0.0.1; # Localhost

# Block everything else
deny all;

proxy_pass http://backend;
}

# Blacklist specific IPs
location / {
deny 198.51.100.0/24; # Malicious IP range
deny 203.0.113.99; # Specific attacker IP
allow all; # Allow the rest

proxy_pass http://backend;
}

Global IP Blocking (http block)

http {
# Manage IP list with the geo module
geo $blocked_ip {
default 0;
198.51.100.0/24 1; # Known botnet range
203.0.113.99 1; # Specific attacker
10.0.0.0/8 0; # Keep internal network allowed
}

server {
if ($blocked_ip) {
return 403;
}
}
}

Nginx geo Module — Country-Based Access Control

Uses ngx_http_geoip2_module with the MaxMind GeoIP2 database.

# Download MaxMind GeoIP2 database (free account required)
# https://www.maxmind.com/en/geolite2/signup

# Set up automatic updates on Ubuntu
sudo apt install mmdb-bin geoipupdate

# /etc/GeoIP.conf
AccountID YOUR_ACCOUNT_ID
LicenseKey YOUR_LICENSE_KEY
EditionIDs GeoLite2-Country GeoLite2-City

sudo geoipupdate # Download the database
# Creates /var/lib/GeoIP/GeoLite2-Country.mmdb
# Install ngx_http_geoip2_module (Ubuntu)
sudo apt install libnginx-mod-http-geoip2
# /etc/nginx/nginx.conf — http block

# GeoIP2 database path
geoip2 /var/lib/GeoIP/GeoLite2-Country.mmdb {
$geoip2_country_code country iso_code;
}

# List of allowed country codes
map $geoip2_country_code $allowed_country {
default 0; # Default: block
KR 1; # Allow South Korea
US 1; # Allow USA
JP 1; # Allow Japan
}

server {
location / {
# Return 403 if not an allowed country
if ($allowed_country = 0) {
return 403 "Access denied from your country.";
}
proxy_pass http://backend;
}
}

Whitelist Strategy (Protecting Internal Services)

Admin panels, monitoring dashboards, and internal APIs should be strictly protected with whitelists.

# Admin page: only allow specific IPs
location /admin {
satisfy all; # Must pass both allow and authentication

allow 10.0.0.0/8; # Internal network
allow 203.0.113.0/24; # VPN range
deny all;

auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;

proxy_pass http://backend;
}

# Monitoring endpoints: allow internal network only
location /actuator {
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;

proxy_pass http://backend;
}

# Static file upload directory: prevent script execution
location /uploads/ {
# Block script type requests
location ~* \.(php|py|sh|pl|rb|jsp)$ {
deny all;
}
alias /var/www/uploads/;
}

Apache Access Control

Require Directive (Apache 2.4+)

# /etc/apache2/sites-available/admin.conf

<Location "/admin">
# Allow only specific IPs
<RequireAny>
Require ip 192.168.1.0/24
Require ip 203.0.113.10
Require ip 127.0.0.1
</RequireAny>
</Location>

# IP blocking (blacklist)
<Location "/">
<RequireAll>
Require all granted
Require not ip 198.51.100.0/24
Require not ip 203.0.113.99
</RequireAll>
</Location>

Country Blocking with mod_geoip2

sudo apt install libapache2-mod-geoip
sudo a2enmod geoip
# /etc/apache2/conf-available/geoip.conf

GeoIPEnable On
GeoIPDBFile /var/lib/GeoIP/GeoLite2-Country.mmdb

<Location "/">
# Allow only South Korea, USA, Japan
SetEnvIf GEOIP_COUNTRY_CODE KR AllowCountry
SetEnvIf GEOIP_COUNTRY_CODE US AllowCountry
SetEnvIf GEOIP_COUNTRY_CODE JP AllowCountry

<RequireAny>
Require env AllowCountry
Require ip 127.0.0.1 # Always allow localhost
</RequireAny>
</Location>

Dynamic IP Blocking Automation

Analyze logs to automatically block attacking IPs.

#!/bin/bash
# /usr/local/bin/auto-ban.sh

# Block IPs that made 100+ requests in the past minute
LOG="/var/log/nginx/access.log"
THRESHOLD=100
PERIOD=60 # seconds

# Count requests per IP from the last minute
awk -v since="$(date -d "$PERIOD seconds ago" '+%d/%b/%Y:%H:%M:%S')" \
'$4 > "["since' "$LOG" | \
awk '{print $1}' | sort | uniq -c | sort -rn | \
awk -v threshold="$THRESHOLD" '$1 >= threshold {print $2}' | \
while read ip; do
# Immediately block with iptables
iptables -I INPUT -s "$ip" -j DROP
echo "$(date): Banned $ip (exceeded $THRESHOLD req/min)" >> /var/log/auto-ban.log
done
# Run every minute via cron
* * * * * /usr/local/bin/auto-ban.sh

# More precise auto-blocking with fail2ban
sudo apt install fail2ban

Access Control Verification

# Verify access from an allowed IP
curl -I https://example.com/admin/
# HTTP/2 200

# Simulate access from a blocked IP (X-Forwarded-For header)
curl -H "X-Forwarded-For: 198.51.100.1" https://example.com/
# HTTP/2 403

# Verify Nginx configuration
sudo nginx -t && sudo systemctl reload nginx

# Check country blocking
curl -H "X-Forwarded-For: 1.1.1.1" https://example.com/
# Need to verify $geoip2_country_code