Regex for IPv4 addresses: matching 0 to 255 properly
An IPv4 address is four numbers from 0 to 255. Here's why the obvious \d{1,3} pattern lets 999.999.999.999 through, how to match one octet with alternation, what to do about leading zeros, and how to find addresses inside text.

· 7 min read · Updated
/^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/
match
- 0
- 42
- 199
- 255
spare
- 256
- 300
- 07
- 1000
An IPv4 address is four numbers separated by dots, like 192.168.1.1. Each number is called an octet, and it runs from 0 to 255.
That sounds easy to match, and the first pattern most people write is short and wrong. This post starts there, fixes it one step at a time, and ends with a pattern for finding addresses inside text. Every example is live, so edit the patterns and watch the lines change.
/^ ( (25[0-5]| 2[0-4]\d| 1\d\d| [1-9]?\d) \.){3} (25[0-5]| 2[0-4]\d| 1\d\d| [1-9]?\d)$/Four numbers from 0 to 255 separated by dots, with no leading zeros.
Accepts
- 192.168.1.1
- 10.0.0.1
- 255.255.255.0
Rejects
- 256.1.1.1
- 010.0.0.1
- 1.2.3
01The obvious pattern
Four groups of one to three digits, with dots in between:
/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/Should match4/4
- 192.168.1.1
- 8.8.8.8
- 999.999.999.999
- 300.1.2.3
Should not2/2
- 1.2.3
- 1.2.3.4.5
It does catch the wrong number of parts. And when you're scanning logs you trust, that may be all you need. But as a validator it accepts anything up to 999.
The repetition can be shorter. Three octets each end in a dot, so group "octet plus dot" and repeat it three times with {3}:
/^(\d{1,3}\.){3}\d{1,3}$/02Don't forget the backslash
In a pattern, a bare . means "any character". Leave out the backslashes and the dots stop meaning dots:
1234567 is the sneaky one. Each . matches a digit, so the pattern reads it as 1, 3, 5, 7 with 2, 4, 6 as the "dots".
03One octet, 0 to 255
A regex has no idea what a number is worth. It only sees characters. So "0 to 255" has to be spelled out as text rules, split into ranges that each have a simple shape:
| Range | Pattern | Reads as |
|---|---|---|
| 250–255 | 25[0-5] |
25, then 0 to 5 |
| 200–249 | 2[0-4]\d |
2, then 0 to 4, then any digit |
| 100–199 | 1\d\d |
1, then any two digits |
| 0–99 | [1-9]?\d |
an optional 1 to 9, then any digit |
Join them with | and wrap them in a group:
/^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/Should match6/6
- 0
- 9
- 42
- 199
- 249
- 255
Should not4/4
- 256
- 300
- 07
- 1000
04Leading zeros
Look at the last branch: [1-9]?\d, not \d{1,2}. That's what rejects 07 and 010. A two-digit octet has to start with 1 to 9.
That matters more than it looks. Some tools read a leading zero as an octal number. Python's socket.inet_aton, which follows the old C networking rules, turns 010.0.0.1 into 8.0.0.1, because octal 10 is 8. If one part of your system reads 010 as ten and another reads it as eight, they disagree about which address they're talking about. Rejecting leading zeros sidesteps the whole question.
05The full pattern
Put the octet group where \d{1,3} used to be:
/^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/Should match4/4
- 0.0.0.0
- 10.0.0.1
- 192.168.1.1
- 255.255.255.0
Should not6/6
- 256.1.1.1
- 192.168.1.300
- 999.999.999.999
- 010.0.0.1
- 1.2.3
- 1.2.3.4.5
It's long, but it's long because the octet is written twice, not because it's complicated. Here's how much the {3} saves compared with writing the octet out four times:
Board0.0.0.0 · 10.0.0.1 · 192.168.1.1 · 255.255.255.0 · 8.8.4.4 vs 256.1.1.1 · 10.0.0 · 1.2.3.4.5 · 192.168.1.300 · 010.0.0.1
- spelled outsolves it140
/^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/ - with a groupsolves it75
/^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/
06Finding addresses inside text
^ and $ only work when the whole input is one address. To find addresses in a log line, the usual move is to swap them for word boundaries, \b. That almost works:
/\b((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\b/Should match2/2
- server at 10.0.0.1.
- version 1.2.3.4.5
Should not1/1
- error 999.1.1.1
What you really want is "no digit or dot right before, and no more address right after". That's a job for lookarounds:
/(?<![\d.])((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?!\.?\d)/Should match2/2
- server at 10.0.0.1.
- ping 8.8.8.8 now
Should not3/3
- version 1.2.3.4.5
- error 999.1.1.1
- build 1.2.3
This one works in Python's re too. Python only allows fixed-width lookbehind, and (?<![\d.]) always checks exactly one character.
07Or use a parser
If you're validating input in code, the standard library already does this. In Python:
import ipaddress
ipaddress.ip_address("192.168.1.1") # IPv4Address('192.168.1.1')
ipaddress.ip_address("192.168.1.1").is_private # True
ipaddress.ip_address("256.1.1.1") # ValueError
Since Python 3.9.5, ipaddress also rejects leading zeros like 010.0.0.1. In Node, net.isIPv4("010.0.0.1") is false too, and it gives the same answers as the full pattern above on every example in this post. A parser also handles IPv6, which is a much bigger regex.
08Try one
The heart of the whole pattern is the octet. Can you write it from memory?
Catch the numbers from 0 to 255, spare everything else.
Targets · 0/10 caught
- 0target
- 7target
- 42target
- 99target
- 100target
- 199target
- 200target
- 249target
- 250target
- 255target
Innocents · 0 hit
- 256innocent
- 260innocent
- 300innocent
- 999innocent
- 1000innocent
- 07innocent
- 007innocent
Type a regex — the board updates as you go.
09Checklist
\d{1,3}allows 0 to 999. Use(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)for one octet.- Escape every dot:
\.. - Repeat with a group:
(octet\.){3}octet. - Use
[1-9]?\dfor the small numbers so leading zeros are rejected. - In text, use
(?<![\d.])and(?!\.?\d)instead of\b. - In code, prefer
ipaddress(Python) ornet.isIPv4(Node).
10Keep practising
- The alternation section of the guide covers the
|that builds the octet. - Try today's hunt, or browse past boards in the archive.