yum upgrades for production use, this is the repository for you.
Active subscription is required.
NGINX basic authentication provides a simple yet effective way to restrict access to your web resources. Whether you need to protect a staging site, secure an admin panel, or add an extra layer of security to sensitive areas, understanding how to properly configure nginx htpasswd authentication is essential for any system administrator.
In this comprehensive guide, you’ll learn everything about implementing nginx basic auth—from creating password files with htpasswd to advanced configurations for protecting WordPress admin areas and staging environments.
Understanding How NGINX Basic Auth Works
Before diving into configuration, it’s worth understanding what happens under the hood. When a client requests a protected resource, NGINX checks for the Authorization header. If missing, it returns a 401 Unauthorized response with a WWW-Authenticate: Basic realm="Your Realm" header, prompting the browser to display a login dialog.
The browser then sends credentials as a Base64-encoded username:password string. NGINX decodes this and compares the password hash against entries in your htpasswd file.
Looking at the NGINX source code (ngx_http_auth_basic_module.c), the authentication happens in the access phase—after URL rewriting but before content generation. This is important because it means authentication is enforced regardless of what content handler processes the request. For more on how location blocks are matched, see our guide on NGINX location priority.
Supported Password Hash Formats
NGINX supports multiple password hash formats through its ngx_crypt() function:
| Format | Prefix | Security Level | Notes |
|---|---|---|---|
| APR1 MD5 | $apr1$ |
Moderate | Apache-compatible, 1000 iterations |
| bcrypt | $2y$ |
High | CPU-intensive, recommended |
| SHA-512 | $6$ |
High | System crypt, configurable rounds |
| SHA-256 | $5$ |
High | System crypt, configurable rounds |
| SHA-1 | {SHA} |
Low | Deprecated, avoid |
| Plain | {PLAIN} |
None | Development only |
The NGINX source explicitly zeros password buffers after use (ngx_explicit_memzero) to prevent memory disclosure—a detail that shows the developers take security seriously.
Installing htpasswd
The htpasswd utility comes with the Apache HTTP server tools package. Despite its Apache origins, it’s the standard tool for creating password files compatible with NGINX basic auth.
On RHEL/Rocky Linux/AlmaLinux:
dnf install httpd-tools
On Debian/Ubuntu:
apt install apache2-utils
On Alpine Linux:
apk add apache2-utils
Creating Your First htpasswd File
The htpasswd command creates and manages password files. Here’s how to create a new file with your first user:
htpasswd -c /etc/nginx/.htpasswd admin
The -c flag creates a new file (overwriting any existing one). You’ll be prompted to enter and confirm the password:
New password:
Re-type new password:
Adding password for user admin
Important: Only use -c when creating a new file. Using it on an existing file will erase all other users.
Adding Additional Users
To add more users to an existing file, omit the -c flag:
htpasswd /etc/nginx/.htpasswd developer
Non-Interactive Password Creation
For scripts and automation, use the -b flag to provide the password on the command line:
htpasswd -b /etc/nginx/.htpasswd api_user MySecurePassword123
Security note: This exposes the password in your shell history and process list. For better security in scripts, use the -i flag to read from stdin:
echo "MySecurePassword123" | htpasswd -i /etc/nginx/.htpasswd api_user
Choosing a Password Hash Algorithm
By default, htpasswd uses APR1 MD5 hashing. For better security, consider using bcrypt or SHA-512:
Bcrypt (recommended for maximum security):
htpasswd -B /etc/nginx/.htpasswd secure_user
The resulting hash looks like: secure_user:$2y$05$...
You can adjust the bcrypt cost factor (higher = slower but more secure):
htpasswd -B -C 10 /etc/nginx/.htpasswd secure_user
SHA-512 (excellent security with configurable rounds):
htpasswd -5 /etc/nginx/.htpasswd secure_user
The hash prefix is $6$ for SHA-512. You can specify the number of rounds:
htpasswd -5 -r 10000 /etc/nginx/.htpasswd secure_user
Viewing and Managing Users
To see which users exist in your htpasswd file:
cat /etc/nginx/.htpasswd
Output shows usernames and their hashed passwords:
admin:$apr1$vZoPBgxo$aLU58eCINcYB/7JWTF3Kd0
developer:$2y$05$K20V4yqxtMD/yYK8JDYCoOuojts.5.lEUftl7DeTJxAewWU/K0JKS
To delete a user:
htpasswd -D /etc/nginx/.htpasswd developer
To update a user’s password, simply add them again (without -c):
htpasswd /etc/nginx/.htpasswd admin
OpenSSL Alternative for Password Generation
If htpasswd isn’t available, you can use OpenSSL to generate compatible password hashes. This is particularly useful on minimal systems or when you need to generate passwords programmatically.
Generate an APR1 MD5 hash:
openssl passwd -apr1 "yourpassword"
Output: $apr1$1MFCM5cr$D4jG4xLTYqmSIsGkVg.wl/
Generate a SHA-512 hash:
openssl passwd -6 "yourpassword"
Create a complete htpasswd line:
echo "admin:$(openssl passwd -apr1 'yourpassword')" > /etc/nginx/.htpasswd
Each time you run openssl passwd, it generates a different hash due to random salt—this is expected and correct behavior. The salt is embedded in the hash itself.
Basic NGINX Configuration for Password Protection
Now let’s configure NGINX to use your htpasswd file. The two essential directives are:
auth_basic— Sets the authentication realm (the name shown in the browser’s login dialog)auth_basic_user_file— Path to your htpasswd file
Protecting an Entire Server Block
server {
listen 80;
server_name staging.example.com;
root /var/www/staging;
auth_basic "Staging Environment";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
try_files $uri $uri/ =404;
}
}
Every request to this server requires authentication.
Protecting a Specific Location
server {
listen 80;
server_name example.com;
root /var/www/html;
location / {
# Public access
try_files $uri $uri/ =404;
}
location /admin {
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ =404;
}
}
Disabling Auth for Specific Locations
When you need most of a directory protected but specific files public, use auth_basic off:
location /admin {
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ =404;
}
# Allow public access to specific file
location = /admin/health-check.php {
auth_basic off;
try_files $uri =404;
}
Protecting WordPress Admin Areas
WordPress sites are frequent targets for brute-force attacks. Adding nginx basic auth creates a powerful additional barrier before attackers even reach wp-login.php.
Complete WordPress Protection Configuration
server {
listen 80;
server_name example.com;
root /var/www/wordpress;
index index.php;
# Main site - public access
location / {
try_files $uri $uri/ /index.php?$args;
}
# Protect wp-login.php
location = /wp-login.php {
auth_basic "WordPress Login";
auth_basic_user_file /etc/nginx/.htpasswd;
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# Protect wp-admin directory
location /wp-admin {
auth_basic "WordPress Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
# CRITICAL: Allow admin-ajax.php without auth
# Many plugins and themes use AJAX on the frontend
location = /wp-admin/admin-ajax.php {
auth_basic off;
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# PHP handling for other files
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Why exclude admin-ajax.php? WordPress uses admin-ajax.php for frontend AJAX requests—contact forms, live search, add-to-cart functionality, and countless plugin features depend on it. Requiring authentication would break these features for regular visitors.
Protecting xmlrpc.php
The XML-RPC endpoint is another common attack vector. If you don’t use it (most modern WordPress setups don’t), block it entirely:
location = /xmlrpc.php {
deny all;
return 444;
}
If you need XML-RPC (for Jetpack or the WordPress mobile app), protect it with basic auth:
location = /xmlrpc.php {
auth_basic "XML-RPC Access";
auth_basic_user_file /etc/nginx/.htpasswd;
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Protecting Staging and Development Sites
Staging sites often contain sensitive data or features not ready for public viewing. Basic authentication is an effective way to restrict access without complex application-level changes.
Full Staging Site Protection
server {
listen 80;
server_name staging.example.com;
root /var/www/staging;
index index.php index.html;
# Require authentication for everything
auth_basic "Staging - Authorized Personnel Only";
auth_basic_user_file /etc/nginx/.htpasswd_staging;
# Your normal location blocks follow
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Different Credentials for Different Environments
Maintain separate htpasswd files for different purposes:
# Production admin access
htpasswd -cB /etc/nginx/.htpasswd_admin admin
# Staging site access (shared with QA team)
htpasswd -cB /etc/nginx/.htpasswd_staging qa_team
htpasswd -B /etc/nginx/.htpasswd_staging developer1
htpasswd -B /etc/nginx/.htpasswd_staging developer2
# Client preview access
htpasswd -cB /etc/nginx/.htpasswd_preview client
Combining Basic Auth with SSL/TLS
Basic authentication sends credentials in Base64 encoding—easily decoded if intercepted. Always use HTTPS when implementing basic auth. For a complete guide on hardening your TLS configuration, see our article on NGINX TLS 1.3 hardening.
Secure HTTPS Configuration with Basic Auth
server {
listen 80;
server_name secure.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name secure.example.com;
ssl_certificate /etc/letsencrypt/live/secure.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/secure.example.com/privkey.pem;
# Modern SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
root /var/www/secure;
# Now basic auth is transmitted over encrypted connection
auth_basic "Secure Area";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
try_files $uri $uri/ =404;
}
}
The HTTP to HTTPS redirect ensures credentials are never sent over an unencrypted connection.
IP-Based Access with Basic Auth Fallback
For internal applications, you might want to allow access without authentication from trusted IP addresses while requiring credentials from everywhere else.
location /admin {
satisfy any;
# Allow without auth from office IP
allow 203.0.113.50;
# Allow without auth from VPN range
allow 10.8.0.0/24;
deny all;
# Require auth from all other IPs
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ =404;
}
The satisfy any directive means the request is allowed if either the IP is whitelisted or valid credentials are provided. Use satisfy all if you want to require both conditions.
Troubleshooting Common Issues
Authentication Not Being Enforced
If requests succeed without credentials, check:
- File permissions: NGINX worker process (usually
nginxuser) must read the htpasswd file:
chmod 644 /etc/nginx/.htpasswd
chown root:nginx /etc/nginx/.htpasswd
- File path: Use absolute paths in
auth_basic_user_file
