Htpasswd Generator
Build .htpasswd lines for Apache or Nginx basic auth — bcrypt, APR1 or SHA-1, hashed on this device.
≈ — per user at cost 12 on this device
—Server configuration
AuthType Basic
AuthName "Restricted"
AuthUserFile /etc/apache2/.htpasswd
Require valid-userPress ? for shortcuts · Never stored — a username and password are credentials. This page keeps nothing between visits. · Link carries settings only — never usernames, passwords or hashes.
This page loads no third-party code — no ads, no trackers, no analytics. Verify in your Network tab.
Do it offline:$ htpasswd -c -B -C 12 /etc/apache2/.htpasswd alice
How to use it
- Type the username. A colon, a line break or a leading # is refused on the spot — each one breaks the record.
- Type the password; the field stays masked until you press the show toggle.
- Pick the format: bcrypt (default, cost 12, labelled $2y$), $apr1$, or {SHA}.
- Press Generate (⌘/Ctrl + Enter), then copy the record or download the generated file.
- Fill in the realm and the file's path on the server to get the matching Apache or nginx block.
Turn a username and password into the exact line a .htpasswd file needs, in the format your server reads: bcrypt, Apache's $apr1$ or the RFC 2307 {SHA} form. Add rows for a whole file, then copy the matching Apache or nginx directives. Hashing happens on this device: nothing is uploaded and nothing is stored.
How it works
A .htpasswd file is one record per line: the username, a colon, then the hash — which is why a colon in a name is fatal. Each format has a fixed width. $apr1$ with its 8-character salt and 22 encoded characters is always 37; {SHA} with 28 base64 characters is 33; a bcrypt record is 60. Files use LF endings and a final newline, so an editor round-trip cannot swallow the last account.
Format is a property of the server, not of taste. Apache httpd reads bcrypt, $apr1$, {SHA} and, on Unix only, crypt(3); plaintext works on Windows, NetWare and TPF alone. Nginx parses $apr1$ itself, accepts the {PLAIN}, {SHA} and {SSHA} spellings from 1.0.3 onward, and otherwise hands the hash to the system crypt() — so on nginx it is bcrypt whose support depends on the box. htpasswd -B writes cost 5; this page writes 12.
Examples
alice · correct horse battery staplealice:$2y$12$sU6R.i.yLQPtSJnKUGScLeACh12VPR0nhkivWHjKWcKG6mRA1VszWA real cost-12 record, accepted by htpasswd -vb. Yours will differ: each run draws its own salt.
webmaster · correct horse battery staplewebmaster:$apr1$IthomiaS$I.skjPfIvYN504/1kqMU7/Salt pinned so openssl passwd -apr1 -salt IthomiaS reproduces it; live runs draw 8 random crypt-64 characters.
deploy · correct horse battery stapledeploy:{SHA}q/eq1kOINtvlJqojGr3i0O73TUI=No salt, so two users sharing this password would share this exact string.
alice · s3cret-one
bob · s3cret-twoalice:$2y$12$wzPQM5GpI0iEkBDVoyQkBOKS/HL9v4e30GbgwGfN0tFEZbzl160zC
bob:$2y$12$uAQ4CHj3DLAoV/B2iS23qeSr.oYxkgtAt5711iybtgXbmfNrl7OUK
Two rows, one file: distinct salts, LF endings, trailing newline. Up to 20 rows per run.
ops · aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaops:$apr1$12345678$3J5H3mHrLw0yfjKzvhS.A/Seventy-three bytes, every one of them hashed: APR1 has no cut-off. Under bcrypt this input keeps 72 and says so in a chip.
alice:admin · s3cretUsername contains a colon (:) at character 6 — the server splits each .htpasswd record at the first colon, so everything after it would be read as the hash. Remove the colon.Refused rather than escaped: the colon is the field separator.
alice · (password field left empty)Password is empty — an .htpasswd record with an empty password lets anyone in with a blank field, and this tool's bcrypt core refuses zero-length input. Enter at least one character.An empty password would admit anyone who leaves the box blank.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
A line break would write a second record
Carriage returns and newlines are refused in the username and in the snippet's realm and path fields: one break turns the rest of the value into a directive or a record nobody asked for.
Spaces are kept, never trimmed
A name typed with a stray space is warned about and written as typed: the server compares bytes, so trimming it quietly would hand you an account nobody can sign into. Non-ASCII names get a warning too — browsers disagree on how to encode them.
htpasswd(1) stops before the format does
The command line refuses a name or a password past 255 characters, and its whole-line buffer refuses at a 194-character username beside a 60-character bcrypt hash ("resultant record too long") — long before that name limit. The file format has neither ceiling.
The first matching record wins
Apache stops at the first line whose name matches, so a repeated username is warned about: the later row's password can never be accepted.
The CLI line asks, so the shell never sees it
The command below takes no password argument; htpasswd prompts for it, keeping the credential out of shell history and out of the process list. Drop the -c once the file exists, or it is rewritten from scratch.
Common errors
Username is empty — an .htpasswd record is username:hash, so a record with no name can never match a login. Enter the username this password belongs to.- No name, no record — nothing for the server to match.
Username contains a colon (:) at character 6 — the server splits each .htpasswd record at the first colon, so everything after it would be read as the hash. Remove the colon.- The position names the first colon in the name as typed.
Username contains a newline (U+000A) at character 6 — one record is one line, so a break here would write a second record that nobody controls. Remove the line break.- Fires for a carriage return too, naming whichever break came first.
Username contains a null byte (U+0000) at character 4 — the server reads each .htpasswd record as a C string and stops at the first null, so this record and every record after it in the file becomes unreadable. Remove it.- One null and htpasswd -vb rejects the whole file, later users included.
Username starts with # — Apache reads a line beginning with # as a comment, so this account would silently not exist and the login would just fail. Remove the leading # or choose another name.- The nastiest one: the file looks right and the login still fails.
Username is longer than htpasswd(1)'s 255-character limit ("username too long") — a record the command line cannot write is a record you cannot maintain there either. Shorten it to 255 characters or fewer.- Measured against the limit the command line enforces.
Password contains a null byte (U+0000) at character 5 — Apache, APR and OpenSSL read the password as a C string and stop at the first null, so everything after it would be ignored and the same password would hash differently across systems. Remove it.- Usually a paste from a UTF-16 buffer; the position marks the null.
Realm is empty — the realm is the text the browser shows in its login prompt, so an empty one leaves the visitor guessing what they are signing into. Enter a short description of the protected area.- From the snippet card: the login box shows nothing else.
File path is empty — the server has to be told where the .htpasswd file lives. Enter the path the file will have on the server, not on this machine.- From the snippet card: a directive without a path configures nothing.
FAQ
Which hash format should I pick for my server?
On Apache httpd, bcrypt: read everywhere httpd runs, and what htpasswd -B produces. On nginx, $apr1$ is the safe default, because nginx implements it directly while bcrypt reaches it only through the system crypt(). Choose {SHA} when an appliance accepts nothing else.
Where should the .htpasswd file live?
Anywhere the server can read and the web cannot — outside the document root, so no request can fetch it. Give AuthUserFile an absolute path: Apache resolves a relative one against ServerRoot and nginx against its own prefix, rarely where you left the file.
Does Basic auth encrypt anything?
No. Under RFC 7617 the browser sends the username and password joined by a colon and base64-encoded — a spelling, not a cipher, and anyone who can read the request can decode it. Serve every protected path over HTTPS: the hashes in the file protect the file, not the connection.
Keyboard shortcuts
- ⌘/Ctrl + Enter
- Generate the record or the whole file.
- ⌘/Ctrl + S
- Download the generated file instead of saving the page.
- ⌘/Ctrl + C
- Copy the generated output when nothing is selected and focus is in the tool — never the password.
- ⌘/Ctrl + Shift + C
- Copy the output even while a selection is active.
- ? or ⌘/Ctrl + /
- Open the shortcut help overlay.