HTTP Basic Auth with htpasswd on Nginx and Apache
Introduction¶
Basic auth is the quickest way to gate a staging environment, an internal
dashboard, or a /metrics endpoint. It's fine for that as long as it's over
HTTPS (Basic sends user:pass base64-encoded on every request — trivially
readable without TLS) and you use a strong hash in the file.
Create the file¶
# bcrypt (-B) — the strong choice. -c creates the file (omit to append).
sudo htpasswd -B -c /etc/nginx/.htpasswd alice
# add another user (no -c)
sudo htpasswd -B /etc/nginx/.htpasswd bob
# non-interactive (careful: password in shell history / process list)
sudo htpasswd -B -b /etc/nginx/.htpasswd ci-user "$CI_PASSWORD"
Hash choices: bcrypt ($2y$) — use this. apr1 ($apr1$, MD5) — the
old Apache default, still widely supported, far better than nothing.
SHA-1 ({SHA}) — unsalted, weak, avoid. Plain crypt — DES, don't.
No htpasswd binary handy? The .htpasswd generator
produces apr1 and SHA-1 lines in the browser.
Lock it down:
sudo chown root:www-data /etc/nginx/.htpasswd
sudo chmod 640 /etc/nginx/.htpasswd
Nginx¶
server {
# ... TLS config ...
location / {
auth_basic "Staging - authorised users only";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend;
}
# Exempt the health check and ACME challenge
location = /healthz {
auth_basic off;
return 200 "ok\n";
}
location /.well-known/acme-challenge/ {
auth_basic off;
root /var/www/certbot;
}
}
Nginx needs to be built with bcrypt support (via the system crypt(3), which all mainstream packages have on Linux). If bcrypt hashes are rejected, the package is old — apr1 will still work.
IP allowlist OR password¶
"Internal IPs get in automatically, everyone else needs the password":
location /admin/ {
satisfy any; # pass if EITHER check passes
allow 10.0.0.0/8;
allow 203.0.113.7/32;
deny all;
auth_basic "Admin";
auth_basic_user_file /etc/nginx/.htpasswd;
}
satisfy all (the default) would require both.
Apache¶
<Directory "/var/www/staging">
AuthType Basic
AuthName "Staging - authorised users only"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
</Directory>
# Or restrict to named users:
# Require user alice bob
IP-or-password in Apache 2.4:
<RequireAny>
Require ip 10.0.0.0/8
Require valid-user
</RequireAny>
.htpasswd files placed in a web directory are ignored by Apache config but
could be served — keep them outside the docroot regardless.
Verification and troubleshooting¶
# Should be 401 without creds, 200 with
curl -sI https://staging.example.com | head -1
curl -sI -u alice:secret https://staging.example.com | head -1
- Always 401 even with correct password — the hash type in
.htpasswdisn't supported by the server build (old Nginx + bcrypt), the file path is wrong, or the server can't read it (perms — it runs aswww-data, not root).nginx -twon't catch a bad path; check the error log:no user/password was providedvspassword mismatch. - 401 loop in a browser — cached bad credentials. The realm string changed, or the browser is retrying. Clear site data.
- Works for
/but not sub-paths (Apache) —AllowOverridefor.htaccessisn't enabled and the directive is in.htaccess; put it in the vhost. - Basic auth header stripped — a proxy in front isn't forwarding
Authorization. In Nginx,proxy_passforwards it by default; aproxy_set_header Authorization "";somewhere is clearing it. htpasswd: command not found— installapache2-utils(Debian) /httpd-tools(RHEL), or use the generator.- Password visible in logs —
-bputs it in your shell history and the process list. Prefer interactive, or feed it via stdin:htpasswd -B -i file user. - Basic auth over plain HTTP — anyone on the path reads the credentials. Redirect all HTTP to HTTPS first; see An Nginx reverse proxy with TLS.
Related tools and reading¶
- On-site: .htpasswd Generator, Hash Generator, Security Headers Checker.
- Related posts: How Nginx location block matching works, Rate limiting in Nginx.
Stuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.More in Web & Mail Servers
Running BIND 9 as an Authoritative Server
BIND as an authoritative server: zone files, secured AXFR with TSIG, SOA serial rules, and DNSSEC inline-signing. Here's a working master/slave setup.
October 24, 2026Varnish Cache in Front of a Site
Varnish is a caching HTTP accelerator configured in VCL. Here's the request lifecycle, why you strip cookies, how bans work, and grace mode for serving stale during backend trouble.
October 24, 2026Let's Encrypt with Certbot, Including Renewal That Doesn't Break
Getting the first cert is easy; keeping renewal working for years is the part people get wrong. Webroot vs DNS-01, deploy hooks, and the failure modes.
October 24, 2026