Nginx Admin Access Control: IP Allowlist and Basic Auth Dual Protection
Public admin panels are the primary target for brute force and exploit. Full Nginx admin access control: allow/deny IP allowlist, auth_basic password auth, satisfy combination logic, and request rate limiting. Includes htpasswd generation and a three-layer verification method for WordPress admin, DB panels, and similar management entry points.
WordPress admin, database panels, monitoring systems—once exposed to the public internet, they become the primary target for brute force and exploit. This article covers full Nginx admin access control: IP allowlist plus Basic Auth, shrinking the admin surface to the minimum. Includes satisfy combination logic, htpasswd generation, and verification.
Applicable Scenarios
- WordPress / Typecho and similar CMS wp-admin and login pages that must restrict source
- phpMyAdmin, Adminer and similar DB panels that must not be reachable by strangers
- Zabbix, Grafana and similar internal monitoring login pages that need an extra auth layer
- Office egress IP is static; want intranet pass-through and internet deny, or password required outside the allowlist
Prerequisites
- Nginx 1.x any mainstream build; http_auth_basic is compiled in by default
- htpasswd installed (httpd-tools; without Apache, openssl can generate hashes)
- A known list of admin static egress IPs; if IPs are not static, use password-only or dynamic DNS
How It Works
Nginx provides two access-control directives: allow / deny match on IP at the network layer, top-down, first hit wins; auth_basic requires HTTP Basic credentials. Credentials travel Base64-encoded, so they must be used with HTTPS.
Combination logic is controlled by satisfy: `satisfy all` (default) requires both IP allowlist and password—fits admins whose IP may change; a new egress IP can still log in with the password. `satisfy any` grants access if either condition passes. That enables passwordless entry from company IPs. The reverse requirement—everyone else blocked even with a password—is usually implemented as allowlist pass-through, remaining IPs deny all. Production recommendation: satisfy all dual check, defense in depth.
Procedure
Step 1: Generate the password file
# Method 1: htpasswd
yum install -y httpd-tools # Debian/Ubuntu: apt install apache2-utils
htpasswd -cB /etc/nginx/.htpasswd admin # -c first create, -B bcrypt
# Method 2: openssl (no httpd-tools)
printf "admin:$(openssl passwd -apr1 'YourStrongPwd!')" > /etc/nginx/.htpasswd
chmod 640 /etc/nginx/.htpasswd
chown root:nginx /etc/nginx/.htpasswd
-B forces bcrypt, stronger against brute force than default MD5 crypt. Keep the password file outside the web root so it cannot be downloaded.
Step 2: Configure location access control
server {
listen 443 ssl;
server_name example.com;
# WordPress admin
location ^~ /wp-login.php {
allow 203.0.113.10; # office egress IP
allow 198.51.100.0/24; # branch subnet
allow 127.0.0.1;
deny all;
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;
include enable-php.conf; # PHP proxy config for the actual environment
}
# DB admin panel: allowlist only, no password
location ^~ /phpmyadmin/ {
allow 203.0.113.10;
deny all;
root /data/www;
}
}
Key points: location `^~` prefix match outranks regex, so this control is not stolen by another regex location. wp-login.php is a standalone file and needs its own location; protecting only `/wp-admin/` misses the login entry. `deny all` is required; addresses that miss every `allow` are not rejected without it.
Step 3: Force HTTPS and limit attempt rate
Basic Auth credentials are Base64 plaintext. Site-wide HTTPS is a prerequisite. Rate-limit 401/403 responses to cut brute-force efficiency:
http {
limit_req_zone $binary_remote_addr zone=adminlimit:10m rate=5r/m;
}
server {
location ^~ /wp-login.php {
limit_req zone=adminlimit burst=3 nodelay;
# ... allow/deny and auth_basic config from above
}
}
rate=5r/m means the same IP gets at most 5 requests per minute. Enough for a real admin; scripted brute force largely fails.
Verification
nginx -t && nginx -s reload
# Allowlist IP + correct password: expect 200
curl -I -u admin:YourStrongPwd! https://example.com/wp-login.php
# IP outside allowlist: expect 403, and no password prompt
curl -I https://example.com/wp-login.php
# Allowlist IP + wrong password: expect 401
curl -I -u admin:wrongpwd https://example.com/wp-login.php
Results of 200 / 403 / 401 respectively mean satisfy all logic is in effect. Then confirm in a browser that the auth dialog appears, and run one full admin workflow to verify no functional impact.
FAQ
FAQ 1: After config, admin login redirect loops, or uploads return 413?
Redirect loops usually mean the PHP location was not included, so the request is served as static. 413 is the default 1M `client_max_body_size`; add `client_max_body_size 64m;` in that location or the http block. If the auth_basic location includes fastcgi, copy the full PHP handling config; otherwise even allowlisted requests cannot execute PHP.
FAQ 2: Admin home broadband IP changes often. What then?
Three options, in priority order: a dynamically resolving hostname with `resolver` and variable allow (needs GeoDNS/script updates); keep auth_basic only and add fail2ban on 401 to ban brute-force IPs; use a VPN with a fixed egress. Do not open large ISP ranges for convenience—that abandons the IP layer.
Summary
Admin panels are the attacker's first target. Nginx allow/deny plus auth_basic compresses the exposure surface by an order of magnitude in a dozen lines. Deploy satisfy all dual check, site-wide HTTPS, and request rate limiting as a three-layer stack. Recheck allowlist IPs on a schedule; update immediately on employee departure or office-network moves so the policy does not rot.