.htpasswd Generator
Generate secure password hashes for Apache and Nginx basic authentication. bcrypt with adjustable cost, APR1 MD5, SHA-1. Multiple users in one pass. Hash verification included. Nothing leaves your browser.
Generate .htpasswd entries
lock All hashing runs locally in your browser. Passwords never touch a server.
Verify an existing hash
Paste a line from an existing .htpasswd file and enter the password to check whether the hash was generated by that password. Useful for confirming a hash copied from a server or generated by another tool.
Nothing leaves your device. Every hash is computed in JavaScript on your machine. There is no upload step, no server-side logging, and no retention after you close the tab. If you want to confirm this, load the page, disconnect from the internet, and generate a hash — it will still work.
What an .htpasswd file actually is
An .htpasswd file is a plain text file where each line is one user, in the
form username:passwordhash. Apache and Nginx read this file
when a protected URL is requested and compare what the browser sends in the
Authorization header against what is stored. The plaintext
password is never kept in the file — only a one-way digest of it.
A typical file with two users looks like this:
alice:$2y$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewKyY3qVOi7aetK2 bob:$apr1$rOioh4Wh$bVD3DRwksETubcpEH90ww0
The prefix of the hash tells the server which algorithm to use. $2y$
(or $2a$, $2b$) is bcrypt, $apr1$ is
Apache's own MD5 variant, and {SHA} is unsalted SHA-1. A single
file can mix formats — Apache and Nginx read the prefix and delegate to the
right algorithm per line. That is useful when migrating an old file to a
newer algorithm without forcing every user to reset their password.
Which algorithm to pick in 2026
The choice is narrower than it looks. Only bcrypt is still considered secure by current standards. Everything else exists for compatibility with older software.
| Format | Prefix | Salted | Nginx support | Use when |
|---|---|---|---|---|
| bcrypt | $2y$ |
Yes | Via system crypt() | Any new deployment. This is the default recommendation. |
| APR1 MD5 | $apr1$ |
Yes (weak) | Native | Nginx on Alpine or any build where bcrypt via crypt() is unavailable. Also useful for maximum portability. |
| SHA-1 | {SHA} |
No | Native | Only legacy systems that cannot read anything else. Identical passwords produce identical hashes, which is dangerous. |
| DES crypt | (13 chars) | Weak | Legacy | Not recommended. Truncates the password to 8 characters and is trivially crackable on modern hardware. |
What bcrypt cost actually controls
The cost parameter is a power of two. Cost 10 means 210 = 1,024 hashing rounds. Cost 12 means 4,096. Cost 14 means 16,384. Each step doubles the work required to compute the hash, which doubles both the attacker's time and the legitimate login delay.
OWASP recommends a minimum of 10 and 12 or higher when latency allows. At cost 10 a modern server produces about 10 hashes per second per core. At cost 14 it produces about 0.6. For a login form where users expect instant response, cost 12 is a common sweet spot — roughly 50 to 100 milliseconds of delay per login, which is unnoticeable to the user but 4,096 times slower for an offline attacker than cost 1.
What the generated entry does not do
Hash generation is only one part of the setup. Two other pieces must be handled correctly for the file to be useful and safe:
- Placement. The file must live outside your document root. Apache and Nginx do not block access to dotfiles by default. If
.htpasswdsits inside/var/www/html/, anyone who guesses the path can download the entire file and crack it offline. - Permissions. The web server user must be able to read the file, and nobody else should.
chmod 640with the file owned by root and grouped under the web server user is the standard setup on Debian-family systems. Avoid world-readable or world-writable permissions.
Server setup after generating the file
Copy the generated lines into your .htpasswd file. If the file
does not exist yet, create it — the tool's download button writes it for you.
Then point your web server at it.
Apache
In your virtual host or a .htaccess file inside the protected
directory:
<Location "/admin"> AuthType Basic AuthName "Restricted" AuthUserFile /etc/apache2/.htpasswd Require valid-user </Location>
Reload with apachectl graceful or systemctl reload apache2.
Nginx
In your server or location block:
location /admin {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}
Run nginx -t first to validate the config, then reload with
nginx -s reload.
Docker Registry
The same .htpasswd file works for a private Docker registry:
docker run -d \ -p 5000:5000 \ -v /etc/docker/auth:/auth \ -e "REGISTRY_AUTH=htpasswd" \ -e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \ -e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \ registry:2
Bcrypt is the recommended format for the registry. APR1 also works but is weaker.
Common mistakes that break or weaken the setup
Forgetting the trailing newline
Every entry in an .htpasswd file must end with a newline character. If you generate a single line and paste it without a trailing newline, the next entry you append will concatenate onto the end of the first and both users will stop working. This is a common cause of "my password stopped working after I added a second user." The download button in this tool writes each line with a newline for you.
Basic auth over plain HTTP
Basic Authentication sends the username and password base64-encoded in the
Authorization header on every single request. Base64 is not
encryption. Anyone capturing packets on the network path — a coffee shop
Wi-Fi, a corporate proxy, a compromised router — can decode the credentials
in under a second. Basic auth must always be terminated behind HTTPS.
Using APR1 because that is what the tool defaults to
The command-line htpasswd utility still defaults to APR1 when
you run it without a flag. Many tutorials therefore show APR1 in their
examples, and it has become the de facto default even though it is
noticeably weaker than bcrypt. On a new Apache install, run htpasswd
-B instead, or use this generator with bcrypt selected.
Bcrypt on Alpine Nginx
Nginx does not implement bcrypt itself. It passes the hash to the system's
crypt() function. Alpine Linux uses musl libc, and older
builds of musl do not support bcrypt through crypt(). If you are running
the official nginx:alpine image, recent versions work, but a
custom build may not. When bcrypt fails on Nginx, APR1 MD5 is the correct
fallback — Nginx implements it natively without relying on the system
library.
World-readable .htpasswd
A .htpasswd file readable by every user on the system is a liability. Any compromised application on the same host can read it and run an offline crack. The file should be readable only by the web server user and root. On shared hosting this is usually handled for you, but on a VPS or container you have to set it yourself.
Frequently asked questions
An .htpasswd file is a plain-text file where each line stores one user, in the format username:passwordhash. Web servers like Apache and Nginx read this file when a protected URL is requested and compare the credentials the browser sends against what is stored. The plaintext password is never kept in the file, only its hash.
Choose bcrypt for any new deployment. It is salted, deliberately slow, and the only format Apache still considers secure today. APR1 MD5 (the $apr1$ prefix) is the portable fallback when bcrypt support is not available, which is common on older Nginx builds where bcrypt is delegated to the system crypt() library. SHA-1 and the old DES crypt format should only be used when interfacing with legacy software that cannot read anything else.
OWASP recommends a minimum cost of 10 and 12 or higher when latency allows. Each step in cost doubles the work. A cost of 10 runs 1,024 hashing rounds. A cost of 12 runs 4,096 rounds. Cost 14 takes roughly a second per hash on modern hardware. For a website login, cost 12 is a common balance between security and the delay a legitimate user notices.
Always outside your web root. Apache and Nginx do not block access to dotfiles by default. If .htpasswd sits inside /var/www/html, someone who knows the path can download the whole file and run an offline crack against it. Store it in /etc/ or another directory the web server serves nothing from.
The web server user (www-data on Debian and Ubuntu, apache on RHEL, nginx on some distributions) must be able to read the file. chmod 640 with the file owned by root and grouped under the web server user is the common setup. If the file owner is the same user the web server runs as, 644 is fine. Avoid world-writable permissions.
HTTP Basic Authentication sends the username and password base64-encoded in the Authorization header of every request. Base64 is not encryption. Anyone on the network path can decode it. Basic auth must always be used behind HTTPS. When served over HTTP, the credentials are visible to anyone capturing packets.
Nginx delegates bcrypt verification to the operating system's crypt() function. On Alpine Linux, which uses musl libc, older builds do not support bcrypt through crypt(). The official nginx:alpine Docker image and recent Debian-based images do support it, but a custom build may not. If bcrypt fails, switch to APR1 MD5, which Nginx implements natively without relying on the system library.
No. All hashing runs in your browser using JavaScript. There is no upload, no network request that carries the password, and no server-side logging. You can verify this by loading the page, disconnecting from the internet, and generating a hash — it will still work.