HTTP Basic Auth with htpasswd: Protecting Staging and Admin Paths
Before anyone audits your staging site for vulnerabilities, make them log in twice. A first layer of HTTP Basic auth in front of anything pre-production stops scanners, curious search bots and opportunistic attackers cold, and takes ten minutes to set up. It is not a substitute for application security — it is a cheap, extremely effective outer gate.
How Basic auth works
The server returns 401 Unauthorized with a WWW-Authenticate header; the browser prompts; the client resends the request with an Authorization: Basic header containing base64 of user:password. Two consequences follow: base64 is encoding, not encryption, so Basic auth is only safe over HTTPS; and browsers cache the credentials for the session, so users will not see the prompt again until the browser restarts.
Hash formats: bcrypt over apr1
The password file stores hashes, never passwords. Apache’s classic apr1 (an MD5-based scheme with a distinctive salted prefix) still appears everywhere, but it is a legacy algorithm with low cost per guess. Modern tooling supports bcrypt, which is deliberately slow and configurable:
htpasswd -B -C 10 /etc/nginx/.htpasswd alice htpasswd -bB /etc/nginx/.htpasswd bob S3cret-Rotation
-B selects bcrypt, -C sets the cost. nginx accepts bcrypt entries via the standard auth modules on most distributions; where it does not, apr1 remains the compatible fallback. Rotate credentials by appending a new user or replacing the file atomically — no reload needed, since the file is read per request.
Wiring it up
nginx, protecting an entire server plus its admin path:
location / {
auth_basic "Staging";
auth_basic_user_file /etc/nginx/.htpasswd;
}
location /admin {
auth_basic "Admin area";
auth_basic_user_file /etc/nginx/.htpasswd-admin;
}
Apache equivalent uses AuthType Basic, AuthUserFile and Require valid-user inside a Directory or Location block.
Layering with IP allowlists
The strongest pattern combines both controls. In nginx, the satisfy semantics let you require the allowlist and the password:
satisfy all; allow 203.0.113.7; deny all; auth_basic "Staging"; auth_basic_user_file /etc/nginx/.htpasswd;
Now an attacker needs both a permitted source address and valid credentials. In Apache 2.4 the same logic is Require ip 203.0.113.7 plus Require valid-user wrapped in <RequireAll>. One caution: if the office IP changes, access dies for everyone, so document the allowlist next to the credentials.
Password hashes themselves should never be produced by guessing at openssl flags — the htpasswd generator on WhoDNS creates correct bcrypt or apr1 entries you can paste straight into the password file, including salt handling, so the gate is one config block away.
Operational cautions
Three habits keep this layer painless. First, protect the password file itself: it lives outside the document root and is never served, but confirm your web server config cannot be tricked into serving it. Second, remember Basic auth does not time out on the server side — when someone leaves the team, change their password entry the same day, because browsers will keep replaying cached credentials for the rest of the session. Third, exempt health-check endpoints if your load balancer probes the origin, or monitoring will report the whole site down the moment the gate goes up.