Our recommendation, up front. We make Proxy Mail QA, so we’re not neutral, but it doesn’t choose your password policy. The numbers below come from NIST’s digital identity guidelines (SP 800-63B, fourth revision), which many security teams follow. We recommend Proxy Mail QA for the step after you decide: checking that your form and your server actually do what you decided, with test passwords at exactly the minimum and maximum, one either side, and every kind of symbol. It doesn’t review your password storage or your security as a whole.
The short answer
- At least 15 characters if the password is all someone needs to sign in.
- At least 8 characters if every account also has a second factor, such as a code from an authenticator app.
- Allow at least 64 characters. Longer is fine; a limit in the thousands isn’t needed.
- No rules demanding a capital, a number and a symbol. Length matters more, and those rules make people choose
Password1!. - Refuse common and breached passwords, by checking new ones against a list.
The rest of the guidance, in plain words
NIST’s guidance for passwords chosen by users also says:
- Accept any characters: letters, digits, symbols, spaces and characters from other languages. Normalise Unicode (to NFC) before hashing, so
étyped two different ways is the same password. - Count characters, not bytes, when checking the length.
- Don’t cut passwords short. Check the whole password, never just the start of it.
- Don’t force people to change passwords on a schedule. Ask for a change only when there’s a sign the password was exposed.
- No password hints and no security questions.
- Allow paste and password managers, so people can use long random passwords without typing them.
- Show what was typed on request, so people can check a long password before they submit.
Why have a maximum at all?
A very long password costs your server time to hash, so a maximum stops someone sending megabytes as a password. Set it well above what people use: 64 is the least NIST recommends allowing, and 128 or 256 is common.
Watch your hashing algorithm. bcrypt reads only the first 72 bytes of a password and ignores the rest. Two long passwords that start the same then sign in as each other. A character can be more than one byte (é is two, an emoji is four), so 72 bytes can be fewer than 72 characters. Either:
- use Argon2id (or scrypt), which has no such limit, or
- keep the maximum at a length that fits in 72 bytes, or
- if you must keep bcrypt with longer passwords, hash the password first in a way your security team approves.
Put the rules where the browser can see them
<label for="password">Password</label>
<p id="password-rules">At least 15 characters. Any letters, numbers, symbols or spaces.</p>
<input id="password" name="password" type="password" autocomplete="new-password" minlength="15" maxlength="128" aria-describedby="password-rules" required />
minlengthandmaxlengthlet the browser warn early, and password managers make passwords that fit.maxlengthalso cuts pasted text short without saying so, so set it well above what a password manager would make.autocomplete="new-password"tells password managers to offer a new strong password.- Show the rules before someone types, not only after they get it wrong.
- Enforce the same rules on the server. The browser’s checks are a convenience; anyone can skip them.
- If you use an auth service (Supabase Auth, Clerk, Firebase Authentication, Auth0 and others), set the minimum in its settings as well. Many default to a much shorter one, and a page that says 15 while the service accepts 6 is a common bug.
Check that your form does what you decided
get_password_variants (MCP), qa.passwordVariants() (npm) or GET /v1/variants/passwords (REST API) take your rules (min_length, max_length, and whether symbols are allowed) and return fresh test passwords in three groups:
| Group | With a 15-to-64 policy | What should happen |
|---|---|---|
accept |
Exactly 15 and exactly 64 characters; each kind of symbol; a space inside; éüñ |
Accepted, and signs in afterwards exactly as typed |
reject |
14 characters; 65 characters | Refused, with the rule, before an account is made |
decide |
Only digits; only lower-case letters; a space at the start or end; an emoji; é typed two ways; two passwords sharing their first 72 bytes (when the maximum is over 72) |
Your choice, but decide on purpose. Under NIST, a 15-digit password is allowed unless it’s on your blocklist |
The 72-byte pair is the one to watch: if the second password signs in to the account made with the first, your app is cutting passwords short.
You can run these by hand, from an agent, or in a test:
- By hand: with Proxy Mail QA for Chrome, the card in the password field offers Edge-case password, with lists marked “Should be accepted” and “Should be rejected”, made to fit the field’s own
minlengthandmaxlength. Its everyday passwords fit those rules too. - From an agent: with Proxy Mail QA connected in Qoder (Marketplace):
Our password policy is 15 to 128 characters, any characters allowed. Using Proxy Mail QA,
get_password_variants with min_length 15 and max_length 128, and try each one on
https://staging.acme.dev/signup with a new test address (use a plus tag for each).
For accepted ones, verify the account and sign in with the same password.
Give me a table: what it tests, accepted or refused, the message shown, and whether
signing in worked.
- In a test:
await qa.passwordVariants({ minLength: 15, maxLength: 128 })returnsaccept,rejectanddecide, each a list of{ tests, value }to loop over in Playwright or Cypress.
If refused passwords show no error at all, see Why is my registration form not showing password errors?
What Proxy Mail QA doesn’t do here
- Choose your policy, or tell you whether it meets a law or a standard you’re held to.
- Check how passwords are stored, or test the rest of your security. A security review or penetration test does that.
- Test a blocklist of breached passwords: try a few well-known ones, such as
password1234567, yourself.
Sources: NIST SP 800-63B-4, and its section on authenticators.