Skip to content

htpasswd Generator

An htpasswd generator produces the user:hash lines that Apache, nginx, and Caddy read for HTTP Basic authentication. This one hashes your password in the browser with salted Apache MD5 ($apr1$) or legacy SHA-1, so the plaintext never crosses the network or touches a server log.

Hash algorithm

Everything here runs in your browser with JavaScript. The username and password are never sent to ProbeDNS or any other server — there is no network request behind this button. Reload the page and the values are gone.

bcrypt is the better choice where your server supports it — and it cannot be generated here. bcrypt is deliberately slow and has no pure-JavaScript implementation in the browser standard library, so producing a real $2y$ hash would require shipping a third-party crypto dependency. Rather than hand you something mislabelled, this tool sticks to what it can compute correctly. For bcrypt, run htpasswd -B -C 12 -n username on any machine with Apache 2.4 utilities installed.

What a .htpasswd file actually is

A .htpasswd file is a flat text file with one account per line, in the form username:hash. There is no header, no schema, and no ordering requirement. The colon is the field separator, which is why a username may never contain one. When a request arrives with an Authorization: Basic header, the server base64-decodes the credentials, walks the file until it finds a matching username, and runs the supplied password through whatever algorithm the stored hash declares. The prefix on the hash is what selects the algorithm — $apr1$ means Apache MD5, $2y$ means bcrypt, {SHA} means unsalted SHA-1, and a bare 13-character string means traditional crypt(3) DES.

That prefix-driven dispatch is the reason a single file can mix algorithms freely. You can add a bcrypt line to a file full of $apr1$ lines and Apache will verify both correctly. It also means migrating is painless: rewrite one line at a time as users change their passwords, with no flag day.

Basic auth sends the password base64-encoded, not encrypted. Base64 is trivially reversible — see the Base64 decoder if you want to prove it to yourself. Only ever put Basic auth behind HTTPS, and check the certificate is valid with the SSL certificate checker.

The algorithms, and which to actually use

PrefixAlgorithmSaltedVerdict
$2y$bcryptYesBest choice. Tunable cost, slow by design. Requires Apache 2.4+ / nginx built with crypt support.
$apr1$Apache MD5 (APR1)Yes, 8 charsSafe fallback. 1000 MD5 rounds. Understood by every httpd, nginx, and Caddy build in existence.
{SHA}Unsalted SHA-1NoLegacy only. Identical passwords produce identical hashes; rainbow tables apply.
{plain} / barePlaintext or crypt(3) DESNo / 2 charsNever. DES crypt silently truncates the password to 8 characters.
Read the full explanation of htpasswd generator results ↓

bcrypt is the right answer where your server supports it, and this page deliberately cannot generate it. bcrypt has no implementation in the browser standard library, and producing a real $2y$ hash would mean loading a third-party crypto bundle. Rather than ship something that might be subtly wrong, the tool offers the two algorithms it can compute exactly — and this page tells you the one command that produces bcrypt on any machine with the Apache utilities installed.

APR1 is a genuine second-best rather than a token gesture. It salts with 8 random characters and runs a 1000-iteration MD5 construction, which defeats rainbow tables outright and makes offline cracking meaningfully more expensive than a single hash. Its weakness is speed: modern GPUs try APR1 candidates far faster than bcrypt at a sane cost factor. For a staging site, an internal dashboard, or a temporary maintenance gate, APR1 is fine. For anything protecting real user data, use bcrypt and a long password.

How to read what the tool gives you

An APR1 line looks like deploy:$apr1$k3mQ8vZ1$OyVy2G0hXk9Jc0K7ZQhZE. — three dollar-delimited fields after the username: the magic string, the 8-character salt, and the 22-character digest in Apache's own base64-like alphabet (note it uses . and /, not + and /). Generate the same password twice and you will get two completely different strings. That is the salt doing its job, not a bug, and both will verify.

A SHA-1 line looks like deploy:{SHA}8rFPaOuZX6yzocNSh7d41b14VRE= — the literal token {SHA} followed by standard base64 of the raw 20-byte digest. Because there is no salt, two users with the same password get byte-identical hashes, which leaks information to anyone who reads the file.

Wiring it up

Store the file outside your document root. If .htpasswd sits next to index.html and the server is misconfigured for even a moment, it is downloadable. /etc/nginx/.htpasswd or /etc/apache2/passwd/site are conventional. Lock the permissions to 640 owned by root with the web-server group — the chmod calculator will show you exactly who that lets in.

location /admin/ {
    auth_basic           "Restricted";
    auth_basic_user_file /etc/nginx/.htpasswd;
}
nginx — protect a location block
<Directory "/var/www/html/admin">
    AuthType Basic
    AuthName "Restricted"
    AuthUserFile /etc/apache2/passwd/site
    Require valid-user
</Directory>
Apache 2.4 — in the vhost or a .htaccess file

The command-line equivalents

Everything this page does has a one-line shell equivalent, and on a server you should generally prefer it — there is no browser, no clipboard, and no copy-paste error in the loop. The -n flag prints the line to stdout instead of writing a file; -B selects bcrypt and -C sets its cost.

# bcrypt at cost 12 — the recommended option, printed to stdout
htpasswd -nbB -C 12 deploy 'correct horse battery staple'

# Apache MD5 (APR1), the same algorithm this page implements
htpasswd -nbm deploy 'correct horse battery staple'

# APR1 without Apache installed — openssl generates its own random salt
openssl passwd -apr1 'correct horse battery staple'

# Reproduce a specific hash to check a tool's output against a known salt
openssl passwd -apr1 -salt abcdefgh 'secret123'
# -> $apr1$abcdefgh$aQ26yFH6V5G5PJBY/utXg/

# Create or append to a real file (-c CREATES and TRUNCATES — omit it to append)
htpasswd -B /etc/nginx/.htpasswd deploy
htpasswd -D /etc/nginx/.htpasswd olduser   # delete a user
Generating htpasswd lines with the real tools
The -c flag truncates the file. Running htpasswd -c to add a second user is the single most common way people delete every existing account. Only use -c for the very first user, then never again.

Common failures and their fixes

  • The browser prompts endlessly and never accepts the password. Almost always a bad path in AuthUserFile / auth_basic_user_file, or a file the web-server user cannot read. Check the error log — Apache logs "could not open password file" plainly. sudo -u www-data cat /etc/nginx/.htpasswd proves readability.
  • A password with a `$` or `!` in it fails from the shell but works in the browser. Your shell expanded it. Single-quote the password when passing it to htpasswd -b, or omit -b and let the tool prompt you interactively.
  • Authentication works on one server and fails on another. You generated bcrypt on a host whose nginx was not built against a crypt implementation supporting $2y$. Fall back to $apr1$, which every build understands.
  • Everyone can log in as everyone. A duplicate username. The first matching line wins and later ones are silently ignored — grep the file for duplicates before debugging anything else.
  • The password stopped working after exactly 8 characters. You produced a crypt(3) DES hash (no $ prefix at all). DES crypt truncates. Regenerate with -B or -m.
  • Auth prompt appears on assets, breaking the page. Basic auth applies per-location. Either protect the parent path so CSS and JS are covered too, or explicitly exclude them — a half-protected page loads unstyled.

Choosing the password itself

The hash algorithm sets the cost per guess; the password sets how many guesses are needed. A 12-character random password from a 90-character alphabet is roughly 78 bits — enough that even a fast algorithm like APR1 is not brute-forceable. A dictionary word with a digit appended is crackable against bcrypt at cost 12 in minutes. Generate the password with the password generator, which uses the browser CSPRNG and shows the entropy in bits so you can see what you are getting.

One last note on scope: Basic auth is an all-or-nothing gate with no session, no logout, and no rate limiting. Browsers cache the credentials for the rest of the session, so "logging out" means closing the browser. It is excellent for keeping crawlers and casual visitors out of a staging site, and a poor substitute for a real application login.

Password Generator
Strong random passwords, locally generated

Frequently asked questions

Is my password sent to your server?
No. The hashing runs in your browser in JavaScript — APR1 through a local MD5 implementation and SHA-1 through the built-in Web Crypto API. Open your browser devtools network tab and press Generate: there is no request. That is also why bcrypt is not offered, since it would require loading external code.
Why does the same password produce a different hash every time?
APR1 mixes in a random 8-character salt, which is stored in the middle field of the hash. Different salt, different digest. Both hashes verify the same password. If a generator gives you the *same* string every time for a given password, it is not salting, and you should not use it.
Why can this tool not generate bcrypt?
bcrypt is not part of the Web Crypto API and there is no way to compute it in a browser without shipping a third-party JavaScript library. Adding one would mean asking you to trust code we did not write with a plaintext password. Run htpasswd -nbB -C 12 user password locally instead — it takes one line and the password never leaves your machine either.
Where should the .htpasswd file live?
Outside the document root, always. /etc/nginx/.htpasswd or /etc/apache2/passwd/ are the usual homes. If it must sit inside the web root, add an explicit deny rule for it — but moving it is safer than remembering to keep that rule.
What permissions should the file have?
640 (rw-r-----) owned by root with the group set to the web-server user's group. The server needs to read it; nobody needs to write it at runtime; nobody else needs anything. Use the chmod calculator to confirm what a given mode grants.
Can I use the same file for Apache and nginx?
Yes, as long as the hashes use an algorithm both understand. $apr1$ is universally supported. bcrypt works on Apache 2.4 and on nginx builds linked against a crypt library that handles $2y$, which covers most distribution packages but not all minimal or Alpine-based images — test before relying on it.
Does Basic auth protect against brute force?
Not by itself. There is no lockout, no delay, and no CAPTCHA — an attacker can try credentials as fast as your server answers. Put fail2ban, nginx limit_req, or a WAF rule in front of the protected path if it is exposed to the internet, and use a long random password so speed does not help them.
Should I use `{SHA}` for anything new?
No. It is unsalted, single-pass, and computed billions of times per second on commodity hardware. It exists in this tool only because some appliances and older proxies accept nothing else. If you have that constraint, pair it with a genuinely random 20+ character password so the missing salt matters less.