Regular expressions are usually the first tool people reach for when they need to find PII in files. They're built into every language, every DLP product accepts them, and for well-structured identifiers they work reasonably well. They also produce a lot of false positives, can't validate checksums, and fail badly on things like names and addresses. PII Crawler mostly relies on other techniques for these reasons, but regex still has a real place: feeding rules into tools you already own, matching custom internal identifiers like employee or account numbers, and quick one-off searches.
This page collects regex patterns for common PII types, explains what each one catches and misses, and covers the techniques that separate a usable ruleset from one that buries you in noise. Every pattern here has been tested against sample data.
First, Know Your Regex Engine
Before copying any pattern, know which regex engine will run it. There are two families:
- Backtracking engines (PCRE, Python, Java, JavaScript, .NET) support lookaheads like
(?!000)and backreferences like\1. Most PII regexes you find online assume one of these. - Linear-time engines (RE2, Go's regexp, Rust's regex crate, ripgrep) do not support lookaheads or backreferences. In exchange they guarantee predictable performance on any input, which matters when you're scanning terabytes of files. A pathological pattern that would hang a backtracking engine for minutes simply can't exist here.
If you paste a lookahead pattern into a tool built on a linear-time engine, it will be rejected with an error like look-around, including look-ahead and look-behind, is not supported. PII Crawler's custom regex rules use a linear-time engine, so the patterns below are written to work without lookaheads wherever possible, with stricter backtracking-engine versions noted where they add value.
When testing patterns on regex101, make sure you select the flavor that matches your scanning tool, or you'll validate a pattern your tool can't compile.
Social Security Number (SSN)
SSNs are nine digits, usually formatted NNN-NN-NNNN. Certain values are never issued: area numbers 000, 666, and 900 through 999, group number 00, and serial number 0000. A good SSN pattern excludes them, since encoding these rules is what separates an SSN matcher from a "any nine digits" matcher.
The simple version, which also matches invalid SSNs:
\b\d{3}-\d{2}-\d{4}\b
A strict version that excludes never-issued ranges and works in any engine, including linear-time engines:
\b(00[1-9]|0[1-9][0-9]|[1-5][0-9]{2}|6[0-5][0-9]|66[0-57-9]|6[7-9][0-9]|[78][0-9]{2})-(0[1-9]|[1-9][0-9])-(000[1-9]|00[1-9][0-9]|0[1-9][0-9]{2}|[1-9][0-9]{3})\b
If your engine supports lookaheads and backreferences (PCRE, Python, Java), this version is more compact and also matches unformatted nine-digit runs and space-separated SSNs, while requiring the separator to be consistent:
\b(?!000|666|9\d{2})\d{3}([- ]?)(?!00)\d{2}\1(?!0000)\d{4}\b
Caveats: unformatted SSNs (123456789) are indistinguishable from phone numbers, order numbers, and ZIP+4 codes without surrounding context. And numbers starting with 9 aren't SSNs, but they might be ITINs, covered next.
Individual Taxpayer Identification Number (ITIN)
ITINs look like SSNs but always start with 9 and have a group number in specific ranges (50 to 65, 70 to 88, 90 to 92, and 94 to 99). If your SSN pattern correctly excludes 9xx area numbers, add this pattern so ITINs don't slip through:
\b9[0-9]{2}-(5[0-9]|6[0-5]|7[0-9]|8[0-8]|9[0-2]|9[4-9])-[0-9]{4}\b
Email Address
\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b
This is the pragmatic version. A fully RFC 5322 compliant email regex runs to thousands of characters and still won't tell you whether the address actually exists, so nobody uses one for scanning. The pattern above catches virtually every real-world address you'll encounter in documents.
Caveats: it will match things shaped like emails that aren't, such as [email protected] in web assets or user@host strings in logs and connection strings. It also matches addresses in mailto links and email headers twice if you scan raw HTML or MIME content.
US Phone Number
North American numbers follow the NANP format: a three-digit area code and three-digit exchange that each start with 2 through 9, then four digits. This pattern handles the common formats, including parentheses, dots, dashes, spaces, and an optional +1 country code:
(?:\+?1[-.\s]?)?\(?\b[2-9][0-9]{2}\)?[-.\s]?[2-9][0-9]{2}[-.\s]?[0-9]{4}\b
Matches (212) 555-4567, 212-555-4567, 212.555.4567, +1 212 555 4567, and 2125554567.
Caveats: any unseparated ten-digit run that happens to start with valid NANP digits will match, so account numbers and IDs generate false positives. International numbers outside the NANP need their own patterns.
Credit Card Number
Card numbers have known prefixes per network and fixed lengths. This pattern covers Visa, Mastercard (including the 2221 through 2720 range introduced in 2017, which many older patterns miss), American Express, Diners Club, Discover, and JCB:
\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(?:222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35[0-9]{3})[0-9]{11})\b
Two important caveats. First, this only matches contiguous digits. Cards written as 4111 1111 1111 1111 or 4111-1111-1111-1111 won't match; you either need to normalize the text before scanning or loosen the pattern and accept more noise.
Second, and this is the big one: regex cannot compute the Luhn checksum that every valid card number passes. Roughly one in ten random digit sequences with a valid prefix and length passes Luhn, so a regex-only card detector will flag plenty of tracking numbers, device serials, and database IDs. Any serious card detection validates Luhn in post-processing. This is why PII Crawler detects card numbers with a custom state machine that checks IIN prefixes, lengths, and the checksum together, rather than a regex.
Date of Birth
Dates are easy to match. Knowing a date is a birth date is the hard part.
MM/DD/YYYY and MM-DD-YYYY:
\b(0[1-9]|1[0-2])[/-](0[1-9]|[12][0-9]|3[01])[/-](19|20)[0-9]{2}\b
ISO format (YYYY-MM-DD):
\b(19|20)[0-9]{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\b
On their own these flag every date in every document: invoice dates, deadlines, log timestamps. A date only becomes PII when it's tied to a person, so in practice you need a context requirement, such as the words "DOB", "date of birth", or "born" appearing within a short distance of the match. That's exactly what proximity matching is for, covered below.
IP Address (IPv4)
\b(?:(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b
This correctly limits each octet to 0 through 255, so 256.1.1.1 won't match. It will still match version strings like 1.2.3.4 and internal addresses that may not be sensitive in your context.
US Passport Number
Next Generation US passports, issued since 2021, use one letter followed by eight digits:
\b[A-Z][0-9]{8}\b
Older passports used nine plain digits, which is far too generic to match on its own. If you need to catch those, require a nearby context word like "passport" rather than matching bare nine-digit runs.
Driver's License Number
There is no national format. Every state issues its own, and many state formats are so generic (New York uses nine digits, Texas uses eight) that matching them without context is hopeless. A few states have distinctive formats worth targeting directly:
California: \b[A-Z][0-9]{7}\b
Florida: \b[A-Z][0-9]{12}\b
Illinois: \b[A-Z][0-9]{11}\b
Even these will false-positive on part numbers and internal IDs. The practical approach is to pair the format pattern with a requirement that "driver's license", "driver license", or "DL#" appears nearby. PII Crawler does this internally: a state machine validates the format and a multi-pattern matcher confirms license-related terms are close to the candidate.
US Street Address and ZIP Code
Street addresses are too varied for regex to handle completely (PO boxes, unit numbers, directionals, rural routes), but the common "number, street name, suffix" shape is matchable:
\b[0-9]{1,5}\s+(?:[A-Za-z]+\s+){1,3}(?i:Street|St|Avenue|Ave|Road|Rd|Boulevard|Blvd|Lane|Ln|Drive|Dr|Court|Ct|Place|Pl|Way|Circle|Cir|Loop|Terrace|Ter)\b
ZIP codes, with optional ZIP+4:
\b[0-9]{5}(?:-[0-9]{4})?\b
The ZIP pattern alone matches any five-digit number, which is why matching it in isolation is nearly useless. A ZIP code becomes meaningful when it appears near a city and state name. PII Crawler builds on this idea with city, state, zip clustering: 90210 by itself is just a number, but 90210 near "Beverly Hills" is an address.
Full Name
\b[A-Z][a-z]+\s[A-Z][a-z]+(?:\s[A-Z][a-z]+)?\b
This matches two or three capitalized words, and it is the weakest pattern on this page. Run it against real documents and it happily returns "Main Street", "New York", "Infinite Loop", and every sentence that starts with two capitalized words. It also misses "McDonald", "O'Brien", "van der Berg", hyphenated surnames, all-caps names in forms, and any name that isn't Anglo-formatted.
Names are the clearest case where regex is the wrong tool. Detecting them reliably requires name dictionaries and named-entity recognition, which use surrounding grammar and context to distinguish "April Fields the person" from "april fields the phrase". This is the approach PII Crawler takes.
Bonus: AWS Access Keys
Not PII in the traditional sense, but credentials leak into the same documents and shares that PII does, and they're very regex-friendly because AWS uses fixed prefixes:
\b(?:AKIA|ASIA)[0-9A-Z]{16}\b
AKIA marks long-term access keys and ASIA marks temporary ones. The corresponding secret keys are 40 random base64-ish characters with no anchor, so they're only reliably found near an access key ID or a term like "aws_secret_access_key".
Getting Fewer False Positives
Whatever tool runs your patterns, a few techniques dramatically improve signal:
- Use word boundaries.
\bat both ends prevents your SSN pattern from matching the middle of a 16-digit card number. Every pattern on this page uses them. - Require context. A nine-digit number near the words "SSN" or "Social Security" is worth flagging. The same number in a column of order IDs is not. If your tool supports proximity rules, use them for every low-specificity pattern: dates, ZIP codes, nine-digit runs, license numbers.
- Validate in post-processing. Checksums (Luhn for cards, mod-11 for many national IDs), issue-range rules for SSNs, and calendar validity for dates can't be expressed in regex but eliminate most false positives. If your pipeline allows a validation step after matching, use it.
- Prefer specific over general. One pattern per state license format you actually care about beats a generic pattern that matches half your inventory numbers.
- Test against your real data. Run candidate patterns over a representative sample before deploying them fleet-wide, and tune based on what actually matches. What false-positives in one organization's data is clean in another's.
Where Regex Falls Short
It's worth being clear-eyed about the ceiling. Regex can't compute checksums, so it can't tell a valid card number from a random one. It can't use context, so it can't tell a birth date from an invoice date without bolt-on proximity logic. It can't handle names or addresses in any robust way. And it struggles with OCR output, where a scanned SSN might come back as l23-45-б789.
This is why PII Crawler's built-in detectors use finite state machines, checksum validation, term lists, clustering, and NER instead of regex for the core PII data types. Those techniques encode the validation rules that regex can't express, which is the difference between a report you can act on and a report with ten thousand rows of noise.
Using Custom Regex in PII Crawler
Where regex earns its keep is custom patterns: employee IDs, patient record numbers, contract numbers, and other identifiers unique to your organization that no off-the-shelf detector knows about. PII Crawler supports these in two ways:
- Custom regex rules let you add your own named patterns to any scan.
- Regex proximity groups let you require multiple patterns to appear within a set character distance of each other, which is the context technique described above. An SSN pattern plus an email pattern within 600 characters is a much stronger signal than either alone.
PII Crawler validates patterns with a linear-time regex engine, so use the lookahead-free variants from this page and you can paste them in directly.