Basic authentication in Nginx is a quick way to gate a directory, an admin area, or an entire staging site behind a username and password. It's not a replacement for a full login system, but for internal tools, dev environments, or adding a second layer in front of something already protected, it does the job with almost no overhead.
When to use it
- Staging or development sites you don't want indexed or crawled
- Admin panels (phpMyAdmin, Netdata, etc.) as an extra layer before the application login
- Internal tools and dashboards with no built-in authentication
- API endpoints that only trusted clients should reach
Basic auth sends credentials base64-encoded (not encrypted) with every request — so always pair it with SSL. Never use it over plain HTTP.
Step 1: Create a password file
Use openssl to generate a SHA-512 hashed password — stronger than the old DES-based crypt method and built into every Linux system:
# Generate a SHA-512 hash (prompts for password twice)
openssl passwd -6
Paste the output into a password file in the format username:hash, one per line:
admin:$6$saltsalt$SomeReallyLongHashedStringHere
editor:$6$saltsalt$AnotherHashedString
Save this file outside your web root — /etc/nginx/conf/passwords/ is a good home. Give it restrictive permissions:
chmod 600 /etc/nginx/conf/passwords/mysite
chown root:www-data /etc/nginx/conf/passwords/mysite
If you have apache2-utils installed (or don't mind installing it), htpasswd is a convenient alternative:
# Create a new file and add a user
htpasswd -c /etc/nginx/conf/passwords/mysite admin
# Add another user to an existing file (no -c)
htpasswd /etc/nginx/conf/passwords/mysite editor
By default htpasswd uses bcrypt on modern systems, which is fine.
Step 2: Configure Nginx
Add auth_basic and auth_basic_user_file to the location you want to protect:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name staging.mysite.com;
# ... SSL certificates and root directive ...
location / {
# Normal site content
try_files $uri $uri/ /index.php$is_args$args;
}
location /admin {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/conf/passwords/mysite;
}
# Block hidden files — also protects any stray .htpasswd if left in the web root
location ~ /\. {
deny all;
}
}
auth_basic sets the realm string shown in the browser's password dialog. auth_basic_user_file points to the file you created in Step 1.
Reload Nginx. Visiting /admin now prompts for credentials.
Step 3: Combine with an IP allowlist
You can allow trusted IPs through without a password while requiring authentication from everyone else — useful for your home or office network:
location /admin {
satisfy any;
# Trusted IPs — no password required
allow 203.0.113.50;
allow 192.168.1.0/24;
allow 10.0.0.0/8;
# Everyone else gets the password prompt
deny all;
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/conf/passwords/mysite;
}
satisfy any means "pass either check" — if the IP matches, you're in. If it doesn't, you get the auth prompt. Without satisfy any, the default is satisfy all, which would require both a matching IP and a valid password.
Step 4: Rate-limit login attempts
Basic auth has no lockout mechanism — an attacker can hammer credentials indefinitely. Rate limiting slows them to a crawl:
# In nginx.conf (http block)
limit_req_zone $binary_remote_addr zone=authlimit:10m rate=1r/m;
# In your server block
location /admin {
limit_req zone=authlimit burst=2 nodelay;
error_page 503 =429 /rate_limit_exceeded;
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/conf/passwords/mysite;
}
This caps login attempts at one per minute per IP, with a burst of 2. Nginx returns a 503 when the rate limit is exceeded; the error_page directive maps it to HTTP 429 (Too Many Requests) so the client knows what happened.
Step 5: Add fail2ban for repeated failures
Basic auth failures appear in your Nginx error log. fail2ban can watch for them and block offending IPs at the firewall level. Create a filter at /etc/fail2ban/filter.d/nginx-http-auth.conf:
[Definition]
failregex = ^<HOST> - .* "(GET|POST|HEAD).*" 401
ignoreregex =
Enable it in /etc/fail2ban/jail.local:
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/*error.log
bantime = 3600
maxretry = 5
After five 401 responses in the bantime window, the IP gets firewall-blocked for an hour.
A note on password file placement
Store your password files outside the web root. If you must keep one inside a web-accessible directory, the location ~ /\. block shown in Step 2 will deny access to any file starting with a dot. But it's safer to keep them entirely elsewhere — /etc/nginx/conf/passwords/ with chmod 600 is the standard approach.