How to use the Htpasswd Generator

  1. Enter a username and a password, or generate a strong password.
  2. Choose the hash: bcrypt where the server supports it, otherwise apr1.
  3. Copy the line into the .htpasswd file on the server.
  4. Add the auth_basic (Nginx) or AuthUserFile (Apache) lines, test the config and reload.
  5. Use Check a password to confirm a password matches an existing line.

What an htpasswd file is

An .htpasswd file holds one user per line as username:hash, and Nginx's auth_basic_user_file or Apache's AuthUserFile reads it to protect a site or a path with HTTP Basic authentication (RFC 7617). The file stores a hash of each password, not the password: bcrypt ($2y$), Apache MD5 ($apr1$) or SHA-1 ({SHA}), and this generator writes each one exactly as Apache's htpasswd command does.

Basic auth is the quickest way to keep a staging site, an admin panel or a metrics endpoint away from the public and from search engines. It is not a user system: there is no logout, no lockout and no password reset.

The hash formats

FormatStarts withhtpasswd flagNginxApacheVerdict
bcrypt$2y$-BOnly where the system crypt() has bcrypt2.4 and laterBest: salted and deliberately slow
Apache MD5$apr1$-m (the default)Yes, built inYesWorks everywhere; fast to brute-force
SHA-1{SHA}-sYes, since 1.3.13YesUnsalted: avoid
crypt() DES13 characters-dThrough crypt()YesUses only 8 characters of the password

Nginx implements apr1 and {SHA} itself and hands every other hash to the operating system's crypt(). Distributions that ship libxcrypt, which includes current Debian, Ubuntu and RHEL-family releases, understand $2y$; elsewhere every bcrypt login fails. Test once with curl after setting it up, and fall back to apr1 if it fails.

How the bcrypt cost works

bcrypt runs its key setup 2^cost times, so each step up doubles the work: cost 10 is 1,024 rounds and cost 12 is 4,096. Apache's htpasswd -B defaults to 5 and accepts 4 to 17; this tool defaults to 10. Remember that browsers send Basic credentials with every request, and the server checks the hash every time, images and scripts included. A cost that takes 250 ms makes a page of 40 assets noticeably slower, so 10 is a sensible ceiling for basic auth.

$2y$ and $2b$ are the same algorithm with different labels: $2y$ is what PHP and Apache write. bcrypt reads only the first 72 bytes of a password; anything after that is ignored.

Worked example: protect a path in Nginx

With Apache's tools installed (apache2-utils on Debian and Ubuntu, httpd-tools on RHEL), create the file and the first user, then add more users without -c, which would overwrite the file:

sudo apt install apache2-utils
sudo htpasswd -c -B -C 10 /etc/nginx/.htpasswd admin
sudo htpasswd -B -C 10 /etc/nginx/.htpasswd deploy

Without them, OpenSSL makes an apr1 hash: printf 'admin:%s\n' "$(openssl passwd -apr1)" | sudo tee -a /etc/nginx/.htpasswd. Then protect the location and reload:

location /admin/ {
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
}
sudo nginx -t && sudo systemctl reload nginx
curl -I https://example.com/admin/                  # 401 Unauthorized
curl -I -u admin https://example.com/admin/         # asks for the password, then 200

For Apache, the same file works inside a <Location> or <Directory> block with AuthType Basic, AuthName, AuthUserFile and Require valid-user; the generator prints the block.

Common errors and how to fix them

  • 403 or 500 and "Permission denied" in the error log: the web server's user cannot read the file. Make it root:www-data (or nginx/apache on RHEL) with mode 640.
  • The right password is refused with bcrypt on Nginx: that server's crypt() has no bcrypt, and the error log shows crypt_r() failed or password mismatch for the user. Use apr1 for Nginx on that server.
  • Login works with curl but not in the browser: the browser cached old credentials. Try a private window.
  • A user with a colon in the name never works: the colon separates name and hash, so it cannot appear in a username.
  • Every user was lost: htpasswd -c recreated the file. Keep -c for the first user only.

Basic auth and Shipown

Shipown sets up Nginx and Certbot for a domain, so the site already has HTTPS before you add basic auth, and it tests the Nginx configuration before every reload, putting back a configuration Nginx rejects (server management). Run sudo nginx -t yourself after adding auth lines by hand. For other Nginx settings, see the Nginx config generator.

Frequently asked questions

How do I create an htpasswd password without Apache installed?

Use this generator, or run openssl passwd -apr1, which prints an Apache MD5 hash to put after "username:". Both work with Nginx and Apache.

Does Nginx support bcrypt in htpasswd files?

Only where the operating system crypt() supports bcrypt, as libxcrypt does on current Debian, Ubuntu and RHEL-family systems. apr1 and {SHA} always work because Nginx implements them itself.

Which htpasswd hash should I use?

bcrypt with a cost around 10 where the server supports it, otherwise apr1. Avoid {SHA}, which is unsalted, and crypt() DES, which uses only eight characters of the password.

Is HTTP basic authentication secure?

Only over HTTPS. The browser sends the password base64-encoded with every request, which is readable to anyone who can see unencrypted traffic.

How do I check a password against an htpasswd file?

Run htpasswd -v /etc/nginx/.htpasswd username, or paste the line into this page in Check a password mode. Both compute the hash again with the stored salt and compare.

Why does the same password give a different hash every time?

bcrypt and apr1 add a random salt to each hash, so two users with the same password get different lines. The salt is stored in the line, which is how a check still works.