htpasswd Generator

Build one username and hash line for an Apache or Nginx .htpasswd basic-auth file, in bcrypt or MD5-APR1, computed inside your own browser tab.

🌐 Español

🔒 Private by design: your text is processed locally in your browser and never uploaded to any server.

Which box the username goes in, and why it is called Original text

This page borrows the two-textarea layout the site’s diff tool uses, so the boxes carry that tool’s labels. The mapping never varies. Original text is the username. Changed text is the password. Nothing about the wording is meaningful here, and rather than ship a second, near-identical form for one more two-field tool, the layout was reused as it stands.

An htpasswd file is a plain text file with one user per line, and each line is a username, a colon, and a hash. That colon is why the username field is validated at all: a colon inside a name would silently split the line into the wrong fields for everything that reads it later.

Filling the boxes and pasting the line into your server

  1. Paste the account name into the box labelled Original text.
  2. Paste the password into the box labelled Changed text.
  3. Leave Hash format on bcrypt (recommended, htpasswd -B), or switch it to MD5-APR1 (legacy, htpasswd -m) if something on your server insists on it.
  4. Set Bcrypt cost factor (2^cost rounds), bcrypt format only if you want something other than 10. The field is bounded at 4 and 12, anything outside that range is pulled back into it, and the whole option is ignored on the MD5-APR1 path.
  5. Click the action button, which carries this page’s own name, htpasswd Generator, then Copy to clipboard.

The result box holds exactly one line and nothing else, because that box is what the copy button targets and htpasswd files have no comment syntax to absorb explanatory text. Paste it as a new line into your password file, then point Apache’s AuthUserFile or Nginx’s auth_basic_user_file at that file.

Cost 10 by default, and what raising it buys

Bcrypt’s cost factor is an exponent: each step up doubles the work. The value here comes straight from the site’s Bcrypt Hash Generator & Verifier, which the module imports its bounds and default from rather than picking its own, so the two pages cannot drift apart. Ten is bcrypt’s long-standing real-world default. Twelve is the ceiling, and that cap is deliberate rather than arbitrary: this is a pure JavaScript implementation with no native code behind it, and the bounds were set from timings measured against this exact library so that one click never turns into a long wait.

For a basic-auth file guarding a staging site, ten is a sensible answer and you can leave the field alone. What matters more than the exact number is that the cost is recorded inside the hash, so Apache keeps verifying old lines correctly after you raise it for new ones.

The $apr1$ format, and why it is not a plain MD5

Pick MD5-APR1 and you get a string that starts with $apr1$, followed by an eight-character salt, another dollar sign, and 22 characters of digest. That is Apache’s own variant of the classic MD5-crypt scheme, the one htpasswd -m writes. It is not the 32-character hexadecimal digest you would get from the MD5 Hash Generator; it salts the input, folds the password length into the mix in a famously odd loop, and then stretches the result through a thousand further MD5 rounds before encoding it in a crypt-style alphabet.

That makes it enormously stronger than a bare MD5 of a password, and still much weaker than bcrypt, because the thousand rounds are fixed forever while bcrypt’s cost is a dial. Keep it for compatibility, not for new work.

Checking the algorithm against openssl passwd -apr1

There is no maintained, correctly-licensed package that implements this specific variant, so it was written by hand from the reference algorithm, which is the sort of code that can be subtly wrong and still look plausible. The check was therefore done against a completely separate implementation. Calling the module’s own hashing function with the password secret123 and the fixed salt abcdefgh returns $apr1$abcdefgh$aQ26yFH6V5G5PJBY/utXg/, and running openssl passwd -apr1 -salt abcdefgh secret123 on the same machine returns that identical string.

You cannot reproduce that from the page itself, and it is worth saying why: the tool draws a new random eight-character salt on every single run, so the fixed-salt comparison had to be made by calling the function directly. Salt reuse would be the bug, not the feature. Several more fixtures covering an empty password, a password longer than sixteen bytes and a multi-byte UTF-8 password are pinned in the test suite for the same reason.

What the tool refuses to do, and what the box says instead

Three inputs are rejected before any hashing happens, and each one comes back as readable text in the result box rather than as a failure. An empty username is refused and names the box to fill. A username containing a colon is refused, because that character is the field separator. An empty password is refused and names its box too. Every one of those is written as report text on purpose, since the shared layout replaces any thrown error with a single generic sentence and would otherwise swallow the explanation.

Leading and trailing spaces around the username are trimmed away before the line is built. The password is not trimmed, because a space can legitimately be part of a password, so watch for one arriving with a paste.

Credentials worth generating on the machine that uses them

Plenty of sites offering this exact function compute the hash on their own backend, which means typing a real server password into a form and posting it somewhere. Everything here runs in the tab, which is the only defensible design for a page whose entire input is a credential. The same reasoning drives the SSH Key Generator and the SSL CSR Generator, both of which mint private key material locally.

Two neighbours are worth a moment while you are here. The password file itself should not be world-readable, and the Chmod Calculator translates between the octal and symbolic notation you will need for that. If you are protecting a site rather than a directory, the Nginx & .htaccess Config Generator writes the surrounding server block. The wider developer tools category has the rest.

See it in action

Screenshot of the htpasswd Generator tool with the sample input “sysfenix-demo”, Hash format set to bcrypt (recommended, htpasswd -B), Bcrypt cost factor (2^cost rounds), bcrypt format only set to 10
htpasswd Generator mid-process: the sample input “sysfenix-demo”, Hash format set to bcrypt (recommended, htpasswd -B), Bcrypt cost factor (2^cost rounds), bcrypt format only set to 10.
Screenshot of the htpasswd Generator result screen showing the generated output “sysfenix-demo:$2y$10$peAeskQkt8PzxKFLmD/45O7GrTE…”
The finished result: the generated output “sysfenix-demo:$2y$10$peAeskQkt8PzxKFLmD/45O7GrTE…”. The download link is a local blob URL — the file never leaves your device.

Frequently asked questions

The two boxes are labelled Original text and Changed text. Which one is the username?

The first one. Original text takes the username and Changed text takes the password, always in that order. Those labels belong to the shared two-box layout this page borrows and have nothing to do with htpasswd files. Leave one of them empty and the result box names the missing field rather than failing with a banner.

I ran the same password twice and got two different hashes. Which one should I keep?

Either. Both formats draw a fresh random salt on every run, and the salt is stored inside the hash string itself, so two runs of one password genuinely cannot match. That is how password hashing is supposed to behave. Apache reads the salt back out of the stored line when it checks a login, so any of the generated lines will accept the same password.

Why does the bcrypt output carry a $2y marker rather than $2a or $2b?

The bcryptjs library underneath emits $2b and offers no way to ask for anything else, while Apache's own htpasswd -B writes $2y. The module rewrites the marker after hashing so the line looks like one Apache produced. For any password shorter than 256 bytes the three markers describe byte-identical computations, so this is a cosmetic relabelling and not a different algorithm.

I emptied the cost field and the hash appeared much faster. What cost did it actually use?

Four, the weakest setting this tool allows. An empty number input reads as zero, and the module clamps anything under four up to four and anything over twelve down to twelve, rounding fractions to the nearest whole number along the way. Zero therefore lands on four rather than falling back to the default of ten. Type the number you want instead of clearing the field.

Can I generate lines for several users at once?

No. One run reads exactly two boxes and returns exactly one line, so a team means one run per person. The secondary button after a run reads Compare another, which is the shared layout's wording rather than a description of what this page does, and it clears both boxes ready for the next user.

Is MD5-APR1 still a reasonable choice for a new file?

Only when something specifically demands it, such as an old Apache install or a script that parses the $apr1$ prefix. It is a salted algorithm with a thousand stretching rounds, so it is far from a bare MD5 digest, but that round count is fixed and cannot be raised as hardware gets faster. Bcrypt is the default here because its cost factor can be.

Related tools