Regex for a strong password (and why length matters more)
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{12,}$ requires a lowercase letter, an uppercase letter, a digit, a symbol, and at least 12 characters. NIST's current guidance says not to impose character-type rules at all. Require length and reject known-breached passwords instead.
JavaScript ✓PCRE ✓
No catastrophic-backtracking patterns found. (A heuristic check: nested and adjacent repeats, overlapping alternatives.)
^Start of the stringanchor(?=…)Lookahead: the text that follows must match this, but it is not consumedlookaround.Any character except a line breakcharacter type*Repeat it zero or more times, as many as possible (greedy)quantifier[a-z]One character: a-z (lowercase letters)character class(?=…)Lookahead: the text that follows must match this, but it is not consumedlookaround.Any character except a line breakcharacter type*Repeat it zero or more times, as many as possible (greedy)quantifier[A-Z]One character: A-Z (uppercase letters)character class(?=…)Lookahead: the text that follows must match this, but it is not consumedlookaround.Any character except a line breakcharacter type*Repeat it zero or more times, as many as possible (greedy)quantifier\dA digit (0-9)character type(?=…)Lookahead: the text that follows must match this, but it is not consumedlookaround.Any character except a line breakcharacter type*Repeat it zero or more times, as many as possible (greedy)quantifier[^A-Za-z0-9]Any one character except A-Z (uppercase letters), a-z (lowercase letters) and 0-9 (digits)character class.Any character except a line breakcharacter type{12,}Repeat it 12 or more times, as many as possible (greedy)quantifier$End of the stringanchor
Examples
Checked at build time in JavaScript and in Python's re.
| Input | Matches? |
|---|---|
Tr0ub4dor&3xx | ✓ match |
Password123! | ✓ match |
correct horse battery staple | ✗ no match |
Sh0rt! | ✗ no match |
alllowercase1! | ✗ no match |
NoDigitsHere!! | ✗ no match |
NoSymbols12345 | ✗ no match |
Ünïcödé123!xyz | ✗ no match |
Try your own text
One string per line: each line is tested on its own (the m flag is added here).
How it works
Each (?=.*X) is a lookahead. It scans ahead from the start for an X without consuming anything, so the engine is back at position 0 for the next check. The four lookaheads are four independent conditions. The final .{12,} does the actual matching and enforces the length.
NIST SP 800-63B is the US federal guideline on passwords. Since its 2017 edition it has advised against composition rules, because people meet them in predictable ways: Password1! passes this regex. Revision 4, finalized in 2025, makes that a requirement (verifiers SHALL NOT impose such rules). It asks for a minimum of 15 characters when the password is the only factor, 8 when it is used with multi-factor authentication, and a maximum of at least 64. It also requires checking new passwords against a list of known-compromised ones.
Variants
/^[\s\S]{15,}$/uLength only, at least 15 characters, counting each emoji as one character. Pair it with a breached-password check./^(?=.*[A-Za-z])(?=.*\d).{8,}$/A common older rule: at least 8 characters with a letter and a digit.
Gotchas
Password123!passes andcorrect horse battery staplefails. The second is far harder to guess. That is the problem with composition rules.- The classes are ASCII-only.
Ünïcödé123!xyzfails becauseÜis not in[A-Z], while[^A-Za-z0-9]countsÜas a symbol. A space also counts as a symbol. .does not match line breaks, and without theuflag an emoji counts as two characters toward the length.- Never put a maximum below 64 and never strip characters silently. Both break password managers.
Related explainers
- Catastrophic backtracking, explained with ^(a+)+$Why ^(a+)+$ takes exponential time on input like aaaa...b, how nested quantifiers cause ReDoS, and the...
- Greedy vs lazy quantifiers: .* vs .*?How greedy .* and lazy .*? differ, shown on quoted strings, why a negated class like [^"]* is often better...
- Regex to trim leading and trailing whitespaceHow ^\s+|\s+$ with the g flag trims a string, why String.prototype.trim() is better, and the 2016 Stack...