Matching US Social Security Numbers with Regex
A US Social Security Number is a nine-digit identifier in the format AAA-GG-SSSS — area (3 digits), group (2 digits), serial (4 digits). Regex is a natural fit for validating format, catching typos before they hit your database. But two things make SSN regex trickier than it looks: (1) the SSA has assignment rules that make certain patterns impossible, and (2) SSNs are extremely sensitive personal data — how you handle them in code matters as much as whether the regex is correct. Below are the patterns that work, plus the security context every developer needs before touching this field.
The Core Patterns
Basic SSN format (recommended for most cases)
/^\d{3}-\d{2}-\d{4}$/
// Matches: 123-45-6789 000-00-0000 666-66-6666
// Rejects: 12-345-6789 123456789 1234-56-789
// Accepts obviously-invalid combinations — pair with SSA rule checks.Strict SSA-compliant SSN
/^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/
// Rejects patterns the SSA never issues:
// - Area = 000, 666, or 900-999
// - Group = 00
// - Serial = 0000
// Matches: 123-45-6789
// Rejects: 000-45-6789 666-45-6789 900-45-6789
// 123-00-6789 123-45-0000The three negative lookaheads enforce real SSA numbering rules. In 2011 the SSA switched to randomized assignment, so the old "area = state" rules no longer apply — but the reserved patterns above remain permanently invalid.
SSN without hyphens (9 digits)
/^\d{9}$/
// Matches: 123456789
// Common storage format — hyphens are display-only.SSN accepting hyphens OR spaces OR nothing
/^\d{3}[-\s]?\d{2}[-\s]?\d{4}$/
// Matches: 123-45-6789 123 45 6789 123456789
// Best UX pattern: accept anything users type, normalize before storing.SSN mask (show only last 4)
// Full input: 123-45-6789 → Masked: XXX-XX-6789
ssn.replace(/^\d{3}-\d{2}/, 'XXX-XX')
// Or in a single-pass regex with backreference:
ssn.replace(/^(\d{3}-\d{2})(-\d{4})$/, 'XXX-XX$2')Extract SSN from free-form text
/\b\d{3}-\d{2}-\d{4}\b/g
// Finds every SSN-shaped token in a document.
// Useful for redaction pipelines. Word-boundary \b prevents partial matches.Language-Specific Usage
JavaScript
// Basic format check
const SSN = /^\d{3}-\d{2}-\d{4}$/;
function isSsnFormat(s) { return SSN.test(s); }
// Strict SSA-compliant
const STRICT = /^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/;
function isValidSsn(s) { return STRICT.test(s); }
// Normalize any user input to 9 digits
function normalizeSsn(s) {
return s.replace(/\D/g, ''); // Strip everything except digits
}
// Mask for display
function maskSsn(s) {
return s.replace(/^\d{3}-\d{2}/, 'XXX-XX');
}
isValidSsn('123-45-6789'); // true
isValidSsn('000-45-6789'); // false — invalid area
maskSsn('123-45-6789'); // 'XXX-XX-6789'
normalizeSsn('123 45 6789'); // '123456789'Python
import re
SSN = re.compile(r'^\d{3}-\d{2}-\d{4}$')
STRICT = re.compile(r'^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$')
def is_ssn_format(s: str) -> bool:
return bool(SSN.match(s))
def is_valid_ssn(s: str) -> bool:
return bool(STRICT.match(s))
def mask_ssn(s: str) -> str:
return re.sub(r'^\d{3}-\d{2}', 'XXX-XX', s)
# Extract from text (for redaction)
def find_ssns(text: str) -> list:
return re.findall(r'\b\d{3}-\d{2}-\d{4}\b', text)
is_valid_ssn('123-45-6789') # True
is_valid_ssn('666-45-6789') # False
mask_ssn('123-45-6789') # 'XXX-XX-6789'PHP
function isValidSsn(string $s): bool {
return (bool) preg_match(
'/^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/',
$s
);
}
function maskSsn(string $s): string {
return preg_replace('/^\d{3}-\d{2}/', 'XXX-XX', $s);
}
// Laravel validation rule
$request->validate([
'ssn' => ['required', 'regex:/^(?!000|666|9\d{2})\d{3}-(?!00)\d{2}-(?!0000)\d{4}$/'],
]);Security & Handling — Read This Before Shipping
SSN is one of the most regulated data points in US software. Getting the regex right is easy; getting the surrounding code right is where teams stumble. Non-negotiable rules:
- Never log an SSN in plaintext. Not in application logs, not in analytics events, not in error stack traces. Redact before any
console.log,logger.info, or third-party monitoring call. - Never put an SSN in a URL query parameter. URLs end up in server access logs, browser history, referrer headers, and third-party scripts. Use POST/PUT with a JSON body over HTTPS.
- Encrypt at rest. Store SSNs encrypted with a key managed by a KMS (AWS KMS, Google Cloud KMS, HashiCorp Vault). Rotate the key at least annually.
- Mask in the UI. Show only the last 4 digits by default. Reveal the full SSN only after an explicit, audited user action (with re-auth for sensitive workflows).
- Compliance context. Depending on your industry: PCI-DSS (retail), HIPAA (healthcare), SOC 2 (SaaS), GLBA (finance) — each has specific requirements around SSN handling. Consult your compliance team before shipping.
Common Pitfalls
Regex is not verification
123-45-6789 passes both format regex and strict SSA regex, but it may not correspond to a real, issued SSN. The only way to verify an SSN is to submit it to the SSA Verification Service or a paid provider. Regex prevents typos; it does not prevent fraud.
Cultural context — SSN is US-only
Many countries have similar national ID numbers (UK NI number, Canadian SIN, Australian TFN) but they all have different formats. Never label an international ID input as "SSN" — use "Government ID" and match the appropriate regex for the country.
Test SSNs in fixtures create real risk
Don't use realistic SSN patterns (123-45-6789) in test fixtures or example data. Use the SSA-reserved test range: 987-65-4320 through 987-65-4329. These are guaranteed never to be assigned to real people.
Users type SSN many ways
123-45-6789, 123456789, 123 45 6789, and even 123.45.6789 all show up in real forms. Accept a broad input, normalize to bare digits, then validate — much better UX than rejecting valid SSNs on cosmetic grounds.
SSN Regex Cheatsheet
| Goal | Pattern | Notes |
|---|---|---|
| Basic format | /^\d{3}-\d{2}-\d{4}$/ | form typo check |
| Strict SSA | /^(?!000|666|9\d{2})...(?!00)...(?!0000)...$/ | rejects impossible |
| 9-digit storage | /^\d{9}$/ | no hyphens |
| Any separator | /^\d{3}[-\s]?\d{2}[-\s]?\d{4}$/ | flexible UX |
| Mask last 4 | /^\d{3}-\d{2}/ → XXX-XX | display safe |
Testing Your SSN Regex
Use the live Regex Tester above with these test strings (from the SSA-reserved test range):
- Match (format only):
123-45-6789,987-65-4320 - Match (strict SSA):
001-01-0001,555-12-3456 - Reject (bad area):
000-45-6789,666-45-6789,900-45-6789 - Reject (bad group):
123-00-6789 - Reject (bad serial):
123-45-0000 - Reject (wrong length):
12-345-6789,1234-56-789
Common Mistakes When Writing SSN Regex
- Not anchoring. Without
^and$,abc123-45-6789xyzwould match. Always anchor. - Trusting format for authenticity. A well-formed SSN can still be fake. Use SSA Verification for any workflow that depends on validity.
- Storing with hyphens. Adds no value, wastes 2 bytes per record, and creates comparison bugs. Store 9 digits, format on display.
- Displaying without masking.Even authorised users usually don't need the full SSN. Mask by default.
- Using realistic test SSNs. Use 987-65-4320 through 987-65-4329 in fixtures — SSA reserves these for testing.
Performance Notes
SSN regexes have bounded quantifiers and no ambiguity — they run in constant time regardless of input size. The three negative lookaheads in the strict version add negligible overhead. You can validate millions of SSNs per second. Performance is never the bottleneck; security hygiene is.