I have recently had cause to find regular expressions that allow me to determine if some user input is an IP address, IP address range (in CIDR notation) or a hostname. The IP address and IP address range validators were needed for both IPv4 and IPv6. I found various bits and bobs around the web, and if I’m honest, I can’t remember where I got them from (please let me know if I owe you some attribution!).
But I don’t want to go looking for them again, and I think they will be useful to the wider community, so here they are. Remember that these all have to be entered on one line. You can test them out in your browser using the excellent Regex Pal site.
IPv4 address
^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$
IPv4 CIDR range
[Updated: 13/Jan/2015 – thanks to Mike in the comments for pointing out the bug in the old version!]
[Updated: 6/Apr/2015 – fixed to resolve the problem pointed out by Pirabarlen in the comments]
[Updated: 9/Aug/2016 – fixed the precedence for the part after the forward slash. Thanks to Gavin in the comments]
^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(3[0-2]|[1-2][0-9]|[0-9]))$
IPv6 address
^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*
IPv6 CIDR range
[Updated: 6/Apr/2015 – fixed to resolve the problem pointed out by Pirabarlen in the comments]
[Updated 9/Aug/2016 – changed precedence of matches after slash to go longest to shortest. Thanks to Oleksiy and Rui Lapa in the comments for pointing this out.]
^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$
Hostname
(Updated 9/Aug/2016: Escaped “.” to avoid accepting any random string as a hostname! Thanks to Thomas in the comments for pointing out this error.)
^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9-]*[A-Za-z0-9])$
32 replies on “Regular expressions for IP addresses, CIDR ranges and hostnames”
Thanks for these!
One fix for the IPv4 CIDR – it currently allows for ‘0’ for the mask bits, so 4.4.4.4/0 would be a match.
It should be:
^(?:(?: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]?)(/([1-9]|[1-2]d|3[0-2]))$
Sarmad,
I’m glad you’ve found the page useful. The research that I’ve done suggests that /0 is in fact a valid CIDR mask. If you have a reference to the contrary, I’d be interested to see it! Cheers!
Mark
I found that the mask group is evaluated from left to right (as expected) and that when using these to find CIDR addresses the match stops at the first number when there are more.
For example for 127.0.0.1/32 the match is 127.0.0.1/3.
Fixed by moving the single digit match to the end.
(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/([1-2]d|3[0-2]|d))
And thank you for the patterns I forgot to say.
Good catch – thanks for the update!
According to the first regex (IPv4 address), IP like 192.168.0.0 will consider valid. is that the correct behavior ?
Hi Itamar,
192.168.0.0 is indeed a valid IP address, so long as it is within a subnets usable address range.
For example 192.160.0.0/12 would have usable addresses: 192.160.0.1 – 192.175.255.254
The regex for ipv6 and ipv6 cidr accept “derp” as a valid input. It obviously isn’t because ‘p’ is not hex and it is quite short to be ipv6. I don’t currently know how to generate this stuff, but is there a way to fix this?
Pasting the regex in to Regex Pal, I’m not seeing the behaviour that you are seeing. Since the ranges are specified as [0-9A-Fa-f]{1,4} I can’t see how an ‘r’ or ‘p’ would ever be considered part of a match. Can you provide some examples of what you are seeing?
Hey, Sorry, I’m not skilled enough in regex to provide a fix, but when testing your IPv4 CIDR range on RegEx Pal I was not able to get a match on lots of combinations that I think should work, for example: 10.0.0.0/16 or 123.123.0.0/20 where as these are valid as confirmed by http://ipduh.com/ip/cidr/ (I was able to get a match for others such as 123.123.0.0/32 to confirm I may have been using RegEx Pal correctly.) Thoughts?
Hi Mike,
Good call. It looks like the problem is in the CIDR part after the slash. It should have had “/d” rather than just “d” for the two digit parts. I’ll update the main post in a few moments to fix this. I’ve taken the opportunity to update the rest of the Regex to use /d instead of [0-9].
Thanks Mark, all sorted. Thanks for your page.
I’m using your regex as the start of a validation function in my python script.
Its probably worth your readers noting that the regex checks for the formatting which is super great, but will match on things like 123.123.123.123/20 which technically isn’t valid (it should be something like 123.123.112.0/20).
However I knew what I was looking for, and found it on your page. Thanks!
I’ve actually just switched it all back to using [0-9] throughout since some regex engines seem to want \d and some want just “d”. To avoid confusion, the numeric range seems safer! I have not yet updated the IPv6 regexes though, so I’m wondering if these might be suffering from similar problems.
D0 you have a version of the IPv6 RegExs with the numerical ranges. I can not get them to match anything for me.
You should probably escape the period in the CIDR regex. it matches any character, not just the period character right now.
Thanks Al. I’ve updated both the IPv4 and IPv4 CIDR regexs as they were both allowing any character where it should be just “.”.
Hi, first of all thanks for this page :D, saves a lot of time.
There’s a minor escape issue in your ipv4 CIDR regex, a forward slash is not escaped
original : /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/([0-9]|[1-2][0-9]|3[0-2]))$/
fixed: /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/
rewritten with bold (had no idea if bold would work)
/^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/
Great, Mark! This is really full collection of IP validation regular expressions.
I was looking for such a list all day. Thanx a lot!
Very helpful, thanks for this very precious entry!
I have tried your IPv6 CIDR range expression with the following address but it fails to match. I don’t know why but wondered if you have an explanation?
2620:0:2d0:200::7/32
When using your regex to extract addresses from strings, you find that a subnet of 10.0.0.0/24 is returned as 10.0.0.0/2 because the precedence of the final matching group has single digit matches first. Changing it to this gets around the problem:-
(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/))
Great work though.
[…] to Mark Hatton for the IPv4 regular […]
Hi,
for what is the percent char used in the cidr v6 (%.+)? term used?
In all regex parser i used it is parsed as the literal % ?!?
tommes
Hostname regex has a bug – it currently accepts “foo bar”.
The dot needs to be escaped!
Your regex for “IPv4 CIDR range” only matches first digit of the mask. e.g. for /32 it would match everything up to “/3”. You should have more specific cases evaluated first. This seems to have fixed the issue for me:
(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(3[0-2]|[1-2][0-9]|[0-9]))
Thanks a lot for this work!
But, both CIDR need a little fix on the netmask. They need to go from all possible matches to least possible matches.
IPV4 CIDR should end with (3[0-2]|[1-2][0-9]|[0-9])
IPV6 CIDR should end with (12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9])
Thanks, I’ve been through to tidy up this and a few other issues pointed out in the comments.
[…] would like to give a big thanks to Mark Hatton for giving me a starting point with the IP Regular Expressions. I know Perl and RE, but it’s […]
complete 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]?)
https://github.com/maravento/blackip/blob/master/bipupdate.sh
IPv6 CIDR range does not appear to work for many of these.
https://www.mediawiki.org/wiki/Help:Range_blocks/IPv6
This seems to work for IPv6. So I think just the last part needs to change.
^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))$
[…] http://blog.markhatton.co.uk/2011/03/15/regular-expressions-for-ip-addresses-cidr-ranges-and-hostnam… […]