HTTP basic authentication makes the browser ask for a username and password before showing a page. It’s not a replacement for an application’s own login, but it’s a quick, effective extra layer for staging sites, admin tools, monitoring dashboards and private files.
1. Create a password file
Install the htpasswd tool:
sudo apt install -y apache2-utils # Debian/Ubuntu
sudo dnf install -y httpd-tools # RHEL family
Create the file with a first user (-c creates it — only use it the first time, or you’ll overwrite existing users):
sudo htpasswd -c -B /etc/nginx/.htpasswd alice
sudo htpasswd -B /etc/nginx/.htpasswd bob # add more users
-B uses bcrypt hashing. Without htpasswd, OpenSSL can generate entries (with a weaker hash):
printf "alice:$(openssl passwd -apr1)\n" | sudo tee -a /etc/nginx/.htpasswd
Protect the file:
sudo chown root:www-data /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd
Keep it outside the web root, so it can never be downloaded.
2. Protect a whole site
server {
server_name staging.example.com;
auth_basic "Staging";
auth_basic_user_file /etc/nginx/.htpasswd;
# …
}
The string after auth_basic is the “realm” shown in some browser prompts.
3. Protect one path
location /admin/ {
auth_basic "Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
try_files $uri $uri/ /index.php?$args;
}
For a PHP admin area, the PHP handling must also be protected — otherwise /admin/index.php can be requested directly and matched by the general \.php$ location. Put protection at server level, use a nested PHP location inside the protected one, or use ^~ so regex locations don’t win:
location ^~ /admin/ {
auth_basic "Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
See Nginx location blocks for why this matters.
4. Exclude a path from protection
Inside a protected server, turn it off where needed — for example Let’s Encrypt validation or a health check:
location /.well-known/acme-challenge/ {
auth_basic off;
}
location = /health {
auth_basic off;
return 200 "ok";
}
5. Combine with IP restrictions
Allow your office without a password, require a password for everyone else:
location /admin/ {
satisfy any;
allow 203.0.113.0/24;
deny all;
auth_basic "Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
}
satisfy any = IP or password; satisfy all (default) = IP and password.
6. Apply and test
sudo nginx -t && sudo systemctl reload nginx
curl -I https://staging.example.com/ # 401 Unauthorized
curl -I -u alice:password https://staging.example.com/ # 200
Security notes
- Only over HTTPS. Basic auth sends credentials base64-encoded (not encrypted) with every request. Without TLS anyone on the path can read them. See Let’s Encrypt with Nginx.
- Brute force: add rate limiting to protected locations, or Fail2Ban’s nginx-http-auth jail, which bans IPs with repeated failures.
- No logout: browsers cache credentials until closed. Fine for admin tools; not for user-facing logins.
- Shared accounts: give each person their own entry so you can revoke access individually (
htpasswd -D /etc/nginx/.htpasswd bob).
When to use something stronger
For several internal apps, consider single sign-on with 2FA in front of everything — Authelia or Authentik integrate with Nginx via auth_request. Or keep admin tools off the internet entirely behind a VPN like WireGuard.
Related: Nginx server blocks and WordPress staging sites.