IP Address Validation: Regex, Libraries, and Edge Cases

Validating IP addresses sounds simple. IPv4 has a quirky number of forms; IPv6 is far worse. The right tools and patterns for production code.

IP Address Validation: Regex, Libraries, and Edge Cases

“Validate this string as an IP address.” Sounds trivial. Then you discover IPv4 has at least five textual representations, IPv6 has even more, and the regex you copied from Stack Overflow has been silently passing strings like 999.999.999.999. IP validation is a place where naive code is wrong in subtle ways.

This post covers IP address validation properly: what forms IPv4 and IPv6 take, why regex is rarely the right tool, the standard library functions in major languages, and the edge cases your validation should care about.

Why Validation Is Trickier Than It Looks

A few classic gotchas:

IPv4 has multiple textual forms

  • Standard dotted-decimal: 192.168.1.1
  • Decimal (long): 3232235777 (the same address as 32-bit integer)
  • Hexadecimal: 0xC0A80101
  • Octal: 0300.0250.0001.0001
  • Mixed: 192.0xa8.1.1

Some library functions accept all of these; some accept only the first. Some applications silently exploit the parsing inconsistency.

IPv6 has even more

  • Full: 2001:0db8:0000:0000:0000:0000:0000:0001
  • Shortened: 2001:db8::1
  • Embedded IPv4: ::ffff:192.0.2.1
  • Zone ID: fe80::1%eth0 (link-local with interface specifier)

Comparing 2001:db8::1 to 2001:0db8::0001 for equality requires normalization.

Leading zeros

192.168.001.001 looks fine but some libraries treat leading zeros as octal — 001 is 1, but 010 is 8. This is the source of real CVEs.

Whitespace

" 192.168.1.1" and "192.168.1.1 " — does your validator strip? Reject? Depends on library.

For all these reasons, don’t write your own regex. Use library validators.

The Right Tool in Each Language

Node.js

The built-in net module:

import net from 'node:net'

net.isIP('192.168.1.1')       // 4
net.isIP('2001:db8::1')       // 6
net.isIP('999.999.999.999')   // 0 (invalid)
net.isIPv4('192.168.1.1')     // true
net.isIPv6('2001:db8::1')     // true

Returns the version (4 or 6) or 0 for invalid. Conservative — rejects octal/hex/decimal forms.

Python

The ipaddress module (stdlib):

import ipaddress

ipaddress.ip_address('192.168.1.1')   # IPv4Address('192.168.1.1')
ipaddress.ip_address('2001:db8::1')   # IPv6Address('2001:db8::1')

try:
    ipaddress.ip_address('999.999.999.999')
except ValueError:
    print('invalid')

Strict by default. The ip_address() constructor raises ValueError on invalid input.

Go

The net package:

import "net"

ip := net.ParseIP("192.168.1.1")
if ip == nil {
    // invalid
}
if ip.To4() != nil {
    // it's IPv4
}

Java

java.net.InetAddress:

try {
    InetAddress addr = InetAddress.getByName(input);
    // valid
} catch (UnknownHostException e) {
    // invalid (or DNS lookup failed — note: getByName does DNS!)
}

Warning: InetAddress.getByName() does DNS resolution for non-IP strings. Use InetAddresses.forString() from Guava or InetAddress.getByAddress() with byte arrays for pure validation.

PHP

filter_var with the FILTER_VALIDATE_IP filter:

filter_var('192.168.1.1', FILTER_VALIDATE_IP);  // returns the IP if valid
filter_var('192.168.1.1', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);  // only IPv4
filter_var('192.168.1.1', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE);  // reject private

Rust

The standard library:

use std::net::IpAddr;
let addr: Result<IpAddr, _> = "192.168.1.1".parse();
match addr {
    Ok(ip) => /* valid */,
    Err(_) => /* invalid */,
}

Why use the library

The library handles edge cases consistently. Your regex won’t. Production-grade IP validation should always go through a library function.

What “Valid” Means in Your Context

Beyond “is this a syntactically correct IP,” you usually want additional checks:

Public vs private

Reject RFC 1918 addresses if you only want public IPs. Reject public IPs if you want only internal hosts.

ip = ipaddress.ip_address('192.168.1.1')
if ip.is_private:
    raise ValueError('private IP not allowed')

Specific ranges

Want to ensure the IP is in 203.0.113.0/24?

network = ipaddress.ip_network('203.0.113.0/24')
ip = ipaddress.ip_address(user_input)
if ip not in network:
    raise ValueError('outside allowed range')

Routable on the internet

A public IP that’s not loopback, not multicast, not reserved.

if ip.is_global:
    # it's routable on the public internet

IPv4 only / IPv6 only

Depending on your application:

if not isinstance(ip, ipaddress.IPv4Address):
    raise ValueError('IPv4 only')

Common Validation Mistakes

Regex like ^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$

Accepts 999.999.999.999 because \d{1,3} matches up to 999, not 0-255. This pattern is everywhere on the internet and almost always wrong.

Splitting and converting to int

ip.split('.').every(n => Number(n) >= 0 && Number(n) <= 255)

Misses: leading zeros, hex/octal interpretation by JavaScript, negative numbers ("-1" → -1), NaN quirks, extra parts ("1.2.3.4.5").

Trusting user-supplied IPs

If a user sends an X-Forwarded-For header with a syntactically valid IP, it doesn’t mean the IP is real or the one they’re really coming from. See X-Forwarded-For header.

Not normalizing for comparison

2001:db8::1 and 2001:0db8:0000:0000:0000:0000:0000:0001 are the same address. Use library == after parsing, not string ==.

ip1 = ipaddress.ip_address('2001:db8::1')
ip2 = ipaddress.ip_address('2001:0db8:0000:0000:0000:0000:0000:0001')
ip1 == ip2  # True

Forgetting IPv6

A validator that only accepts IPv4 will silently fail for IPv6 users. In 2026, IPv6 is meaningful traffic. Don’t write IPv4-only validators.

Validation in Web Forms

For user input (e.g., entering an IP for lookup):

  1. Strip whitespace before validating.
  2. Validate with library (not regex).
  3. Reject explicitly with a helpful error message if invalid.
  4. Normalize to canonical form before display/storage.
  5. Don’t trust the IP for security decisions if it came from the user.
function validateIpInput(raw: string): { valid: boolean, normalized?: string, error?: string } {
    const trimmed = raw.trim()
    const version = net.isIP(trimmed)
    if (version === 0) return { valid: false, error: 'Invalid IP address' }
    return { valid: true, normalized: trimmed }
}

Geolocation and Validation

A request to a geolocation API requires a valid IP. The API will validate on its side too, but client-side validation:

  • Avoids unnecessary API calls for clearly bad input.
  • Gives users immediate feedback.
  • Prevents URL injection if the IP is in the URL path.

The Ip2Geo API returns an error for invalid IPs. Your client code should still validate first to provide better UX.

Performance Considerations

Library IP validation is fast — typically sub-microsecond per call. Don’t optimize prematurely with regex. The library is fast enough for any reasonable use case.

If you’re validating millions of IPs per second (unusual): batch them through a stream-friendly parser like ipaddress in Python or netaddr in Go.

The Regex (If You Insist)

For completeness, a correct IPv4 regex:

^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$

For IPv6, the correct regex is over 100 characters of arcane patterns. Don’t write it. Use a library.

TL;DR

  • Use library validators, not regex. net.isIP, ipaddress.ip_address, net.ParseIP, etc.
  • IPv4 has multiple legal forms (dotted, decimal, hex, octal). Most libraries accept only the standard form.
  • IPv6 normalization matters for comparison. ::1 and 0:0:0:0:0:0:0:1 are the same.
  • Validate context beyond syntax: public vs private, routable, specific range, IPv4 vs IPv6.
  • Don’t trust user-supplied IPs for security; validate syntax but don’t trust origin.
  • Leading zeros in IPv4 can be interpreted as octal — a real security risk in naive parsers.
  • For UX, validate client-side; API validates server-side too.

IP validation is one of those tasks that looks simple and isn’t. The good news: every modern language has a library function that gets it right. Use them. For the broader IP-format picture, see everything you need to know about IP addresses; for the IPv4-to-IPv6 transition that means you must accept both, IPv4 vs IPv6 transition.

Get Started

Convert IPs into accurate location data in milliseconds.

Sign up today and get 1,000 free monthly stored conversions, and discover why developers trust us for fast, reliable, and affordable IP conversions.