htpasswd generator

SecurityCreate htpasswd lines for Apache and nginx basic auth in bcrypt, SHA-512 or Apache MD5, and check existing ones. Hashed in your browser, never sent.

Your passwords are hashed on this device and never leave it. Nothing you open is ever uploaded. There is no server to upload it to.

What this does

It makes the password file that Apache and nginx use for basic authentication: one user:hash line per user. You type a username and a password, or generate a strong one, pick a format, and add users one at a time; then you download the file or copy it. It also checks an existing line against a password, which is the quickest way to find out why a login is failing.

The hashing happens in your browser, so the passwords never leave this page. The file holds only hashes.

How to use it

  1. Enter a username, and a password or press “Generate”. Generated passwords are 24 letters and digits with look-alike characters left out, which is about 140 bits of randomness and safe to put in a URL or a command line.
  2. Pick a format. bcrypt is the right choice unless your server is old; see below.
  3. Press “Add to file”, and repeat for each user. Adding a name that is already there replaces its line.
  4. Copy or download the file, and keep a note of each password somewhere safe. The file cannot give them back.

Using the file with Apache

Save the file outside the web root, for example as /etc/apache2/.htpasswd, readable by the user Apache runs as. Then protect a directory, either in its <Directory> block or in an .htaccess file inside it:

AuthType Basic
AuthName "Restricted"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

An .htaccess file only takes effect if the server’s configuration allows it for that directory with AllowOverride AuthConfig (or All).

Using the file with nginx

Save the file as, for example, /etc/nginx/.htpasswd, readable by nginx’s worker user, and add two lines to the location (or server) you want to protect:

location /admin/ {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

Then check the configuration and reload: sudo nginx -t && sudo systemctl reload nginx.

Which format to choose

Format Apache 2.4 nginx Strength
bcrypt ($2y$) Any system Where the system crypt() has bcrypt: current Linux, Alpine Strong
SHA-512 crypt ($6$) Linux and other Unix, not Windows Any Linux Strong
Apache MD5 ($apr1$) Any system Any system Weak if the file leaks

Apache implements bcrypt and Apache MD5 itself, so those work wherever Apache runs, while SHA-512 comes from the operating system’s crypt(), which Windows lacks. nginx implements Apache MD5 and the {SHA}, {SSHA} and {PLAIN} formats itself, and hands crypt-style hashes to the system’s crypt(), so what it accepts depends on the system: bcrypt needs a crypt() that includes it, and SHA-512 is in every Linux one.

Two older formats both servers still read are deliberately not offered: traditional DES crypt, which uses only the first eight characters of a password, and unsalted SHA-1, which falls to precomputed tables.

Why the default cost is 8

bcrypt is slow on purpose, and its cost setting doubles the work with each step. For a login form that runs once per sign-in, a high cost is cheap. Basic auth is different: the browser sends the password with every request, and the server checks it every time, for the page and for each image, stylesheet and script on it. These are the times per check, measured through libxcrypt’s crypt(), the function nginx calls, on an Intel Core i7-10510U laptop processor:

Format Time per request
bcrypt cost 5 (Apache’s own default) 1.4 ms
bcrypt cost 8 (this tool’s default) 10.6 ms
bcrypt cost 10 42 ms
bcrypt cost 12 170 ms
bcrypt cost 14 706 ms
SHA-512 crypt, 5,000 rounds 2.9 ms
MD5 crypt 0.2 ms

At cost 12, a page with thirty images spends five seconds of server time on passwords alone. Cost 8 is eight times Apache’s default and still about a hundredth of a second a request. A server’s own figures scale with its processor, but every step of cost still doubles them.

The cost protects passwords if the file is stolen, by slowing down guessing. A long generated password is beyond guessing at any cost, which is why the tool makes generating one a single click.

Checking a line

Paste a line from a file, or just the hash, with the password you expect, and press “Check”. It recognises every format Apache and nginx read, says whether the password matches, and says what the format means for each server. It checks bcrypt, SHA-512 and SHA-256 crypt, Apache MD5, MD5 crypt, {SHA}, {SSHA} and {PLAIN}. It names, but does not check, DES crypt and yescrypt ($y$, the format many Linux systems now use for their own login passwords).

Every format is tested against independent implementations: the specification’s own test vectors for SHA-crypt, OpenSSL for Apache MD5, MD5 crypt and SHA-crypt, and Python and PHP’s bcrypt and the system crypt() library for bcrypt, including passwords with accented letters and emoji.

Limitations

bcrypt reads only the first 72 bytes of a password. Most servers ignore the rest without saying so; this tool refuses a longer password for bcrypt instead, and suggests SHA-512.

Characters beyond plain ASCII are hashed as UTF-8. A client that sends another encoding will not get in, so letters and digits are the safe choice, and they are what “Generate” uses.

It makes the file; it does not install it. Putting the file on the server and pointing the configuration at it is up to you, as above.

Frequently asked questions

How do I create an htpasswd file?

Enter a username and a password, or press "Generate" for a strong one, and press "Add to file". Add as many users as you need, then download the file or copy it, and put it on your server where Apache or nginx can read it. On a server with Apache's tools installed, `htpasswd -B -C 8 /path/to/file alice` adds the same kind of line to an existing file, and `-c` creates a new one.

Which format should I use for nginx?

bcrypt, if your system supports it, and current Linux distributions and Alpine do: nginx hands the hash to the system's crypt() function, and their crypt libraries include bcrypt. On an older system whose crypt() lacks it, use SHA-512, which every Linux crypt() reads. Apache MD5 works everywhere too, because nginx implements it itself, but it is the weakest of the three.

Does nginx support bcrypt passwords?

Yes, where the system's crypt() does. nginx's documentation lists crypt() hashes, Apache MD5 and a few {SCHEME} formats, and on current Linux the crypt() library, libxcrypt, includes bcrypt, as does musl on Alpine. If logins fail on an older system with a bcrypt line, switch that user to SHA-512.

What bcrypt cost should I use for basic auth?

Lower than for a login page. Basic auth checks the password on every request, every image and stylesheet included, so the cost is paid again and again. At cost 8 that is about 11 ms of server time per request on a laptop processor; at cost 12 it is 170 ms. This tool defaults to 8. With a long generated password the cost barely matters to an attacker anyway.

Why is my basic auth login not working?

Use "Check a line" first: paste the user's line and the password, and it tells you whether they match. If they do, the problem is elsewhere. The usual causes are a wrong path in the config, a file the server's user cannot read, a format the system's crypt() does not support (nginx, older systems, bcrypt), or on Apache an .htaccess file that is ignored because AllowOverride does not include AuthConfig.

Is my password sent anywhere?

No. The hashing runs in your browser, and the file it gives you holds only hashes. Open your browser's network panel while you use the tool and you will see that no request carries your password anywhere.

How do I add or remove a user later?

The file is one line per user, so add a line to add a user, delete one to remove a user, and replace a line to change a password. This tool makes the lines; paste a new one into your existing file. Both servers read the file afresh, so no restart is needed.