Regex for IPv4 Address Validation — Getting the Octet Range Right
IPv4 validation looks trivial until you realize the naive "four groups of digits separated by dots" pattern accepts garbage like 999.999.999.999. This guide walks through the common mistake, the correct fix, and a few real-world variations you will need for CIDR ranges and private IP detection.
The Common Mistake
An IPv4 address is four "octets" (0-255) separated by dots. The tempting shortcut is to match 1-3 digits per octet — but digits alone don't enforce the 0-255 ceiling:
// WRONG — accepts invalid octets above 255
const naiveIp = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
naiveIp.test('192.168.1.1'); // true (correct)
naiveIp.test('999.999.999.999'); // true (WRONG — should be false!)
naiveIp.test('300.1.1.1'); // true (WRONG — 300 is not a valid octet)The Correct Pattern
Each octet needs an explicit alternation that covers three numeric ranges: 250-255, 200-249, and 0-199 (with optional leading zero forms):
const octet = '(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)';
const ipv4Regex = new RegExp('^' + octet + '\\.' + octet + '\\.' + octet + '\\.' + octet + '$');
// Equivalent inline form:
const ipv4 = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
ipv4.test('192.168.1.1'); // true
ipv4.test('255.255.255.255'); // true
ipv4.test('999.999.999.999'); // false — correctly rejected
ipv4.test('300.1.1.1'); // false — correctly rejected
ipv4.test('0.0.0.0'); // trueBreaking down the octet group (25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):
25[0-5]— matches 250 through 2552[0-4][0-9]— matches 200 through 249[01]?[0-9][0-9]?— matches 0 through 199 (with 1-3 digits, optional leading 0/1 hundreds digit)
Reusable Named-Group Version
If you need to extract each octet individually (for logging, sorting, or CIDR math), use named capture groups:
const ipv4Named = /^(?<a>25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?<b>25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?<c>25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?<d>25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const match = '192.168.1.1'.match(ipv4Named);
console.log(match.groups); // { a: '192', b: '168', c: '1', d: '1' }Detecting Private / Reserved IP Ranges
A common follow-up need is checking whether a validated IP falls in a private range (useful for firewall rules, log filtering, or access control):
- 10.0.0.0 – 10.255.255.255 —
/^10\./ - 172.16.0.0 – 172.31.255.255 —
/^172\.(1[6-9]|2[0-9]|3[01])\./ - 192.168.0.0 – 192.168.255.255 —
/^192\.168\./ - 127.0.0.0 – 127.255.255.255 (loopback) —
/^127\./
Frequently Asked Questions
Because \d{1,3} matches any one-to-three digit number, including values above 255 like 999 or 256, which are not valid IPv4 octets (0-255). It needs to be replaced with an explicit numeric range pattern.
A correct pattern is ^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$, which restricts each of the four octets to the valid 0-255 range.
No. IPv6 uses a completely different address format with hexadecimal groups separated by colons, such as 2001:0db8:85a3::8a2e:0370:7334, and needs its own dedicated regex or, better, the built-in URL/net parsing utilities in your language.