Matching MAC Addresses with Regular Expressions
A MAC address (Media Access Control) is a 48-bit hardware identifier assigned to every network interface. It's written as 12 hex digits, usually grouped for readability. The problem for validators is that four different formats are commonly used in the wild — colon-separated (Linux ifconfig), dash-separated (Windows ipconfig), dot-separated (Cisco IOS), and no separators at all (many REST APIs). Below are the patterns that handle each format, plus a combined pattern that accepts all four.
The Core Patterns
Colon or dash separated (most common)
/^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$/
// Matches: 00:1A:2B:3C:4D:5E, 00-1A-2B-3C-4D-5E, aa:bb:cc:dd:ee:ff
// Rejects: 001A2B3C4D5E (no separators)This is the pattern most Linux and macOS tools produce. Note that [:-] as a character class ALLOWS either character on any position — so a mixed 00:1A-2B:3C-4D:5E will match despite being technically invalid.
Strict — same separator throughout
/^([0-9A-Fa-f]{2})([:-])(?:[0-9A-Fa-f]{2}\2){4}[0-9A-Fa-f]{2}$/
// The \2 backreference forces the same separator on all 5 positionsCisco dotted format
/^([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}$/
// Matches: 001A.2B3C.4D5E, aabb.ccdd.eeff
// Cisco IOS format — 3 groups of 4 hex separated by dotsUnseparated (raw hex)
/^[0-9A-Fa-f]{12}$/
// Matches: 001A2B3C4D5E, aabbccddeeff
// Common in APIs — AWS EC2, Cloudflare, and many device registries return MAC this wayAll formats combined
/^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$|^([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}$|^[0-9A-Fa-f]{12}$/i
// Accepts every format seen in the wild.
// Use the i flag so you don't need [A-Fa-f] — just [A-F] with the flag.Language-Specific Usage
JavaScript
const MAC_ANY = /^([0-9A-F]{2}[:-]){5}[0-9A-F]{2}$|^([0-9A-F]{4}\.){2}[0-9A-F]{4}$|^[0-9A-F]{12}$/i;
function isMac(str) {
return MAC_ANY.test(str);
}
isMac('00:1A:2B:3C:4D:5E'); // true
isMac('001A.2B3C.4D5E'); // true (Cisco)
isMac('001A2B3C4D5E'); // true (no sep)
isMac('00:1A:2B:3C:4D:GG'); // false (G is not hex)
// Normalize to canonical colon-separated uppercase
function normalizeMac(mac) {
const hex = mac.replace(/[^0-9A-Fa-f]/g, '').toUpperCase();
if (hex.length !== 12) return null;
return hex.match(/.{2}/g).join(':');
}
normalizeMac('001a.2b3c.4d5e'); // '00:1A:2B:3C:4D:5E'Python
import re
MAC = re.compile(r'^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$'
r'|^([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}$'
r'|^[0-9A-Fa-f]{12}$')
def is_mac(s: str) -> bool:
return bool(MAC.match(s))
is_mac('00:1a:2b:3c:4d:5e') # True
is_mac('001A.2B3C.4D5E') # True
# Extract OUI (first 3 bytes)
def oui(mac: str) -> str | None:
hex_only = re.sub(r'[^0-9A-Fa-f]', '', mac).upper()
if len(hex_only) != 12: return None
return hex_only[:6] # e.g. '001A2B'PHP
function isMac(string $s): bool {
return (bool) preg_match(
'/^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$'
. '|^([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}$'
. '|^[0-9A-Fa-f]{12}$/',
$s
);
}
isMac('00:1A:2B:3C:4D:5E'); // true
isMac('001A2B3C4D5E'); // trueCommon Pitfalls
Mixed separators sneak through
The character class [:-] allows either character at each position. If your input source is untrusted (user-typed field), a mixed value like 00:1A-2B:3C-4D:5E passes. Use a backreference (\2) to force the same separator throughout.
Case sensitivity
MAC addresses are usually written in uppercase but lowercase is equally valid. Always use [A-Fa-f] OR add the i flag — otherwise aa:bb:cc:dd:ee:ff fails.
The regex doesn't validate the OUI
ZZ:ZZ:ZZ:ZZ:ZZ:ZZ fails the regex, but DE:AD:BE:EF:00:00 passes even though DEADBEEF is not a real vendor OUI. For device-registration flows, look up the OUI in the IEEE registry after regex validation.
Broadcast, multicast and locally-administered
FF:FF:FF:FF:FF:FF is the broadcast MAC. 01:xx:xx:xx:xx:xxis multicast. Bit 2 of the first byte indicates locally-administered (as opposed to IEEE-assigned) addresses. Regex won't catch these — check the first byte with bitwise logic if you need to reject them.
MAC Address Cheatsheet
| Format | Pattern | Example |
|---|---|---|
| Colon/dash | /^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$/ | 00:1A:2B:3C:4D:5E |
| Cisco dotted | /^([0-9A-Fa-f]{4}\.){2}[0-9A-Fa-f]{4}$/ | 001A.2B3C.4D5E |
| Unseparated | /^[0-9A-Fa-f]{12}$/ | 001A2B3C4D5E |
| Strict same-sep | /^([0-9A-Fa-f]{2})([:-])(?:[0-9A-Fa-f]{2}\2){4}[0-9A-Fa-f]{2}$/ | only uniform |
| OUI only (first 3 bytes) | /^([0-9A-Fa-f]{2}[:-]){2}[0-9A-Fa-f]{2}$/ | 00:1A:2B |
Testing Your MAC Address Regex
Use the live Regex Tester above with these test strings:
- Match (colon):
00:1A:2B:3C:4D:5E,ff:ff:ff:ff:ff:ff - Match (dash):
00-1A-2B-3C-4D-5E - Match (Cisco):
001A.2B3C.4D5E - Match (raw):
001A2B3C4D5E,DEADBEEF0000 - Reject:
00:1A:2B:3C:4D(5 groups),00:1A:2B:3C:4D:GG - Edge:
00:1A-2B:3C-4D:5E— mixed separators, invalid but passes loose regex
Common Mistakes When Writing MAC Address Regex
A few recurring pitfalls trip up developers writing MAC validators:
- Assuming one format. Colon-separated is common on Linux/macOS, dash-separated on Windows, dotted on Cisco, and unseparated in JSON APIs. If your input could come from any of these, use the combined regex.
- Allowing mixed separators.
[:-]in a character class matches EITHER on every position, so00:1A-2B:3C-4D:5Esilently passes. Use a backreference for strict validation. - Case-sensitive character class.
[A-F0-9]rejects lowercase hex — always use[A-Fa-f0-9]or add theiflag. - Storing MACs in an inconsistent case/format. Normalize on write: strip separators, uppercase, then re-insert colons. Otherwise
aa:bb:cc:dd:ee:ffandAA:BB:CC:DD:EE:FFcompare as different strings. - Not handling EUI-64 (64-bit addresses). IPv6 and some IoT devices use 64-bit hardware IDs written as 16 hex digits. A 48-bit MAC regex will reject them — add a separate pattern if you need to support EUI-64.
Performance Notes
MAC-address regexes are extremely fast because every quantifier is bounded ({2}, {4}, {5}, {12}). No backtracking risk even with the combined all-formats alternation — the engine can decide within a few characters which branch to take. Validating millions of MAC strings takes tens of milliseconds. The one perf trap is calling the regex on every keystroke in a form field with a long alternation pattern — debounce input handling to 100-200ms if you see re-render lag.