本页面有中文版。
Cette page est disponible en français.
Esta página está disponible en español.
Patterns1 live hunt inside

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.

Regex Hunter logo

· 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.

Quick answerJavaScript
/^((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:

Matches6/6 as expected
/^\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
Try it in the Playground
The shape is right, but \d{1,3} means "any number from 0 to 999". The last two lines aren't real addresses.

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}:

Pattern23 chars
/^(\d{1,3}\.){3}\d{1,3}$/
Try it in the Playground
The same pattern as above. The group (\d{1,3}\.) appears three times, then one last octet with no dot after it.

02Don't forget the backslash

In a pattern, a bare . means "any character". Leave out the backslashes and the dots stop meaning dots:

Matches3/3 as expected
/^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$/
  • 1.2.3.4
  • 1a2b3c4
  • 1234567
Try it in the Playground
Both of the last two lines match. The unescaped dots accept letters, and even digits.

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:

Matches10/10 as expected
/^(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
Try it in the Playground
Each branch covers one range. 256 fails all four: 25[0-5] stops at 5, 2[0-4]\d needs 0 to 4 in the middle, 1\d\d needs a leading 1, and [1-9]?\d is at most two digits.

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:

Matches10/10 as expected
/^((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
Try it in the Playground
Three "octet then dot" groups, then a final octet. Every octet must be 0 to 255 with no leading zero.

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:

Compare46% shorter

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

  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)$/
  2. 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)$/
Try the short one
Both versions solve the board. The group with {3} is 75 characters, the spelled-out version 140.

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:

Matches3/3 as expected
/\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
Try it in the Playground
The second line is a version number, not an address, and it still matches. A dot isn't a word character, so there's a boundary between the 4 and the .5.

What you really want is "no digit or dot right before, and no more address right after". That's a job for lookarounds:

Matches5/5 as expected
/(?<![\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
Try it in the Playground
(?<![\d.]) says no digit or dot comes before. (?!\.?\d) says no digit, and no dot followed by a digit, comes after. A full stop at the end of a sentence is still fine.

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?

Live huntpar 35

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.

Try it in the Playground
Like this? A new one drops every day.Play today’s hunt

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]?\d for the small numbers so leading zeros are rejected.
  • In text, use (?<![\d.]) and (?!\.?\d) instead of \b.
  • In code, prefer ipaddress (Python) or net.isIPv4 (Node).

10Keep practising