File Format
One user per line, username and hash separated by a colon:
``
admin:$apr1$xY7kL2mQ$8vN3pR5tW9zA1bC4dE6fG.
deploy:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=
`
Duplicate usernames are ambiguous — Apache honours the first match — so replace an entry
rather than appending a second one for the same user.
The Four Formats
| Format | Prefix | Salted | Strength |
|---|---|---|---|
| bcrypt | $2y$ | Yes | Strongest; htpasswd -B, Apache 2.4+ |
| APR1 (MD5) | $apr1$ | Yes, 1000 rounds | Adequate; portable everywhere |
| SHA-1 | {SHA} | No | Weak; identical passwords hash identically |
| crypt | none | 2-char salt | Broken; truncates at 8 characters |
APR1 is Apache's own MD5-crypt variant. The underlying MD5 is broken for collision
resistance, but APR1's salting and 1000-round iteration make it a genuine (if dated)
password hash rather than a raw digest.Wiring It Up
Apache:
`apache
AuthType Basic
AuthName "Staging"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
`
nginx:
`nginx
location / {
auth_basic "Staging";
auth_basic_user_file /etc/nginx/.htpasswd;
}
`
Doing It on the Server Instead
If you have shell access, generate the file where it will live:
`bash
# Create with bcrypt (best)
htpasswd -B -c /etc/apache2/.htpasswd admin
# Add another user (note: no -c, which would overwrite)
htpasswd -B /etc/apache2/.htpasswd deploy
# APR1, for portability
htpasswd -m /etc/apache2/.htpasswd legacy
`
The -c flag creates a new file, destroying any existing one. Using it twice is the
classic way to lock everyone out.
Security Checklist
Serve over HTTPS only — basic auth is cleartext-equivalent otherwise.- Keep the file outside the document root.
chmod 640and own it by the web server user.- Rotate credentials when someone leaves; there is no session to revoke.
- Do not use basic auth for real user accounts — it has no logout, no lockout and no MFA.
What an .htpasswd File Is
One line per user, a colon between the username and the hash:
`
alice:$apr1$Zx8yQ2mK$3v9pLnR4wT6sK2mB8cN1e/
bob:{SHA}qUqP5cyxm6YcTAhz05Hph5gvu9M=
`
The prefix identifies the algorithm, so a single file can mix formats — which makes
migrating from one to the other straightforward. Both Apache and nginx read the same file.
| Format | Prefix | Salted | Iterated |
|---|---|---|---|
| APR1 (Apache MD5) | $apr1$ | Yes | Yes, 1,000 rounds |
| SHA-1 | {SHA} | No | No |
| bcrypt | $2y$ | Yes | Yes, tunable |
Use [APR1](/dev/htpasswd-generator/apache-htpasswd-generator) unless something
specifically demands otherwise. [{SHA}](/dev/htpasswd-generator/sha1-htpasswd-generator)
exists for LDAP interoperability and legacy appliances; it is unsalted, so identical
passwords produce identical hashes and rainbow tables apply directly.Neither Format Is a Modern Password Hash
APR1 is iterated MD5. A GPU tries billions of MD5 operations per second, so a weak password
behind an APR1 hash falls quickly, and {SHA} is worse. bcrypt is the right choice where
it is available — htpasswd -B produces it — but browsers cannot generate it, because it
needs a native implementation.
The practical consequence: the password must carry the security the hash does not.
Generate a long random one, never reuse it, and treat the hash file as sensitive even though
it is hashed.
Where Basic Auth Belongs
Basic auth is infrastructure gating, not user authentication. It has no session, no logout,
no lockout, no password reset and no rate limiting, and it sends
base64(user:password) on every single request.
Good uses: a staging site, an internal dashboard, a metrics endpoint, a directory you want
kept off the open web. Bad uses: anything with real user accounts.
Over HTTPS the repeated transmission is acceptable. Over plain HTTP it is a plaintext
password on the wire, hundreds of times a session — Base64 is encoding, not encryption.
The Three Configuration Mistakes
1. The password file is inside the web root. Then it can be requested over HTTP and
downloaded, handing over every hash in it. Put it somewhere like
/etc/apache2/.htpasswd, outside anything the server will serve.
2. AllowOverride is not set. Apache silently ignores a .htaccess file it is not
configured to read, so the directory is completely unprotected and nothing logs an error.
Always test with a private browser window before assuming a directory is protected.
3. The path is relative. AuthUserFile needs an absolute path.
The [nginx](/dev/nginx-basic-auth-generator) and
[Apache](/dev/htpasswd-generator/apache-htpasswd-generator) pages each show the
directives for their own server, because the hash is the same and the configuration around
it is not.
Layer Something Else On Top
Where the client set is known, restrict by IP as well as by password — two independent
controls fail independently. Rotate the password when anyone with access leaves, since Basic
auth has no per-user revocation beyond editing the file.
Generated Locally
The salt comes from crypto.getRandomValues()` and the hash is computed in your browser.
The password is never transmitted, which is the entire reason not to use an online generator
that posts it to a server before hashing it.