Page & Bell

.htpasswd Generator

A .htpasswd file stores one username:hash pair per line and powers HTTP Basic Authentication in Apache and nginx. This generator computes bcrypt (the modern default, equivalent to htpasswd -B) or apr1 MD5-crypt (the legacy Apache default) hashes entirely in your browser — a real difference, not a marketing line: most online htpasswd generators POST your plaintext password to their server, which is disqualifying for a credential tool. Here the hash, the multi-user file, and the matching .htaccess and nginx snippets are all produced locally.

Everything is computed in your browser. No password, hash, or keystroke is sent anywhere — unlike most htpasswd generators, which hash on their server.

Algorithm

.htaccess (Apache)

AuthType Basic
AuthName "Restricted Area"
# Absolute filesystem path required — a relative path or URL will NOT work.
# Keep the file OUTSIDE your web root.
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

nginx

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

How to use the .htpasswd generator

  1. Enter a username (no colons — the file format reserves them) and a password, or click Random for a strong one.
  2. Keep bcrypt selected and pick a cost factor (10 is a sound default; each +1 doubles the work an attacker must do per guess).
  3. Click Generate, then copy the user:hash line or add it to the running .htpasswd file to build a multi-user file.
  4. Copy the companion .htaccess or nginx snippet and point AuthUserFile / auth_basic_user_file at the file's absolute path.
  5. Use the Verify tab to test whether a password matches an existing bcrypt or apr1 hash.

Anatomy of the two hash formats

A bcrypt line looks like admin:$2b$10$N9qo8uLOickgx2ZMRZoMye…: $2b$ is the variant, 10 the cost (2^10 = 1,024 rounds), the next 22 characters the salt, the final 31 the digest — all in bcrypt's own Base64 alphabet. An apr1 line looks like admin:$apr1$x7gP2kqM$hashedpart: the magic string, an 8-character salt, then 22 characters encoding the 16-byte digest of 1,000 chained MD5 operations. Passwords are processed as UTF-8 bytes, so non-ASCII passwords work in both schemes; bcrypt additionally truncates input at 72 bytes (the tool warns when that happens).

Why client-side hashing matters here

When a generator hashes on its server, your plaintext password crosses the network, lands in someone's request logs, and you have no way to audit what happens next. The whole point of a password hash is that the plaintext never needs to leave your machine. This page embeds pure-JavaScript implementations of bcrypt (Blowfish with the expensive key schedule, verified against the OpenBSD test vectors) and Apache's MD5-crypt (verified against openssl passwd -apr1), so you can cut the server out entirely. Open your browser's network tab while generating: nothing is sent.

Common failure modes

  • 500 error after enabling auth: AuthUserFile path is relative, or the server user cannot read the file. Check with sudo -u www-data cat /etc/apache2/.htpasswd.
  • Browser re-prompts forever: the hash algorithm is not supported by your server build (typically bcrypt on an old nginx/musl combination) — regenerate as apr1 to confirm.
  • Colon in the username: everything after the first colon is parsed as the hash, so the entry can never match. This tool blocks it at input.
  • Auth over plain HTTP: credentials are Base64 on the wire. Redirect to HTTPS before the auth challenge, not after.

Frequently asked questions

Bcrypt or apr1 — which should I pick?

Bcrypt, unless you run something genuinely ancient. Bcrypt at cost 10 performs 1,024 expensive key-schedule rounds and was designed to be slow; apr1 is 1,000 iterations of MD5, which modern GPUs evaluate billions of times per second. Apache has supported bcrypt (htpasswd -B) since 2.4, and nginx accepts any crypt(3)-style hash the system library understands. Choose apr1 only for compatibility with old shared hosting or Apache 2.2.

What does the bcrypt cost factor actually change?

The hash runs 2^cost expansion rounds, so cost 12 is 16× slower than cost 8 — for you on login and for an attacker per guess. Cost 10 (1,024 rounds) takes roughly 50–100 ms on current servers, a sane ceiling for basic auth since the hash is recomputed on every protected request. Going above 12 can make each page load noticeably laggy; below 8 gives away too much to offline cracking.

Where should the .htpasswd file live?

Outside the web root. If the file sits at /var/www/html/.htpasswd, a misconfiguration (or a server that doesn't block dotfiles) serves your hash list to anyone who requests it. Put it at /etc/apache2/.htpasswd or /etc/nginx/.htpasswd, readable by the server user only (chmod 640, owned root:www-data). AuthUserFile requires an absolute filesystem path — a relative path or URL silently fails with a 500.

Is basic auth safe to use at all in 2026?

Over HTTPS, yes — for the right job. The browser sends username:password Base64-encoded (not encrypted) in the Authorization header on every request, so over plain HTTP anyone on the path reads it. Behind TLS it is a perfectly reasonable gate for staging sites, admin panels, and internal docs. It is not a substitute for application-level auth: no rate limiting, no sessions, no logout, and credentials cached until the browser closes.

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

Each hash embeds a fresh random salt — 16 bytes from crypto.getRandomValues for bcrypt (the 22 characters after the cost), 8 characters for apr1. Salting means two users with the same password get different hashes and precomputed rainbow tables are useless. Verification doesn't compare hashes for equality; it extracts the salt from the stored hash, recomputes, and compares the result — exactly what the Verify tab does.

Does this work for nginx too?

Yes. nginx's auth_basic_user_file reads the same username:hash format. apr1 works everywhere; bcrypt support depends on the system crypt(3) — standard on Debian/Ubuntu and Alpine (musl) builds, absent on some older CentOS images. If nginx rejects logins with bcrypt entries, that is the first thing to check (error log shows "crypt() failed").

Related tools

Learn more