← Blog

Check if an IP address is valid in Python

Python's `ipaddress` module validates and classifies any address for free — here's exactly how far it gets you, and the line parsing can't cross.

Contents

You want to validate an IP address in Python, and you almost certainly do not need a package, a service, or a regex you copied off a forum in 2013. The standard library ships a module that parses IPv4 and IPv6 correctly and tells you what kind of address you're holding. Start there. This post shows exactly how far ipaddress gets you — which is further than most people expect — and where a hosted API is actually adding something instead of reselling what your interpreter already does.

The stdlib already does this: ipaddress.ip_address

ipaddress.ip_address(s) takes a string, returns an IPv4Address or IPv6Address, and raises ValueError if the string isn't a real address. That exception is your validity check — no regex, no length math, no guessing about IPv6 :: compression.

import ipaddress

def is_valid_ip(value: str) -> bool:
    try:
        ipaddress.ip_address(value)
        return True
    except ValueError:
        return False

print(is_valid_ip("8.8.8.8"))          # True
print(is_valid_ip("2606:4700::1111"))  # True
print(is_valid_ip("999.1.1.1"))        # False  (octet out of range)
print(is_valid_ip("192.168.1"))        # False  (not four octets)
print(is_valid_ip("not-an-ip"))        # False

That's the whole answer to "is this a valid IP." A regex would happily pass 999.1.1.1; ipaddress rejects it because it parses the octets, not just the shape. The .version attribute tells you which family you got:

ip = ipaddress.ip_address("2606:4700::1111")
print(ip.version)  # 6

Available since Python 3.3, no install, no dependency to audit.

Classify the address, not just validate it

Valid isn't the same as useful. 203.0.113.9 is a valid IPv4 address and also a documentation range you should never see in production traffic. The parsed object carries boolean properties that answer the questions you actually have:

import ipaddress

def classify(value: str) -> dict:
    ip = ipaddress.ip_address(value)  # raises ValueError if invalid
    return {
        "value": str(ip),
        "version": ip.version,
        "is_private": ip.is_private,
        "is_global": ip.is_global,
        "is_loopback": ip.is_loopback,
        "is_link_local": ip.is_link_local,
        "is_reserved": ip.is_reserved,
        "is_multicast": ip.is_multicast,
    }

print(classify("10.0.0.1"))
# {'value': '10.0.0.1', 'version': 4, 'is_private': True,
#  'is_global': False, 'is_loopback': False, ...}

print(classify("127.0.0.1")["is_loopback"])  # True
print(classify("8.8.8.8")["is_global"])       # True

is_private covers the RFC 1918 ranges plus other non-routable space, is_global tells you it's a publicly routable address, and the rest name the special cases: loopback, link-local (169.254.x / fe80::), multicast, and reserved. If you want the reasoning behind those buckets, what counts as a private IP walks through the ranges. This is real classification, computed locally, for zero cost.

Normalize while you're at it

Two strings can name the same address. Parsing collapses them to one canonical form, which is what you want before storing an IP or comparing two of them:

import ipaddress

print(str(ipaddress.ip_address("2001:0db8:0000:0000:0000:0000:0000:0001")))
# 2001:db8::1

So the stdlib gives you the three things "IP validation" usually means: is it valid, what version, and what kind of address — plus a normalized string. Deduplicate your storage against the normalized value, not the raw input.

When the stdlib is genuinely all you need

If your job is rejecting garbage in a form, blocking private ranges from a webhook target, or sorting inbound addresses into routable vs. not, stop reading and use ipaddress. It's correct, it's free, it has no network dependency, and it can't be down. Doing the same work in JavaScript? The Node version is validate an IP address in Node.js — same idea, different stdlib.

Reach for a service only when the value it adds is a thing your language cannot compute from the address bytes.

Where an API earns its place

Boundstone's /v1/verify/ip runs the exact classification above — same public/private/bogon logic — but over HTTP, and as one contract that also covers phone and email. The point isn't that it validates IPs better than your interpreter. It's that one client, one response shape, and one honesty contract span all three. The is_bogon flag is the piece the stdlib doesn't hand you directly (you'd derive it from is_private, is_reserved, and friends).

import requests

resp = requests.post(
    "https://api.boundstone.io/v1/verify/ip",
    headers={"Authorization": "Bearer bs_live_YOUR_KEY"},
    json={"ip": "8.8.8.8"},
)
data = resp.json()

print(data["valid"], data["version"], data["classification"])
print(data["is_public"], data["is_bogon"])
print("checked:    ", data["checks"]["performed"])
print("not checked:", data["checks"]["not_performed"])
# checked:     ['format', 'version', 'range_classification']
# not checked: ['geolocation', 'asn', 'hosting_datacenter', 'proxy_vpn_tor', 'reputation']

Read checks.not_performed and take it literally. Boundstone does not do IP geolocation, ASN lookup, hosting/datacenter detection, proxy/VPN/Tor detection, or reputation scoring. That intelligence layer needs licensed data and isn't shipped — for anyone, on any plan. When a response says valid, it means format, version, and range classification checked out, and nothing more is being implied. That named list of what we didn't do is the reason to trust the small list of what we did. If you want to see it without a key, the free IP validator tool runs the same classification in the browser.

The short version

  • Validating an IP in Python? ipaddress.ip_address(s) — it raises ValueError on anything invalid. That's the answer.
  • Need version or classification? Read .version, .is_private, .is_global, .is_loopback, .is_reserved, .is_multicast. Free, local, correct.
  • Storing addresses? Normalize with str(ip) first.
  • Want it over HTTP alongside phone and email checks? That's the API — same classification, one contract, 250 credits a month free, no card, credits never expire.
  • Want geo, VPN detection, or reputation? No parser computes that from the address bytes — it takes licensed data, which Boundstone doesn't ship today. Anyone claiming a free "valid" also proves the address is a real user is selling you something the bytes can't support.

Frequently asked questions

How do I check if an IP address is valid in Python?

The simplest way is Python's built-in ipaddress module: call ipaddress.ip_address("8.8.8.8") and it returns an address object for any well-formed IPv4 or IPv6 string and raises a ValueError for anything malformed. Wrap that call in a try/except to turn it into a clean True/False check, no external service or network request needed. This confirms the address is syntactically valid and tells you its version, which is exactly the format-and-version check Boundstone's IP endpoint performs at scale.

How can I tell whether a valid IP is public or private in Python?

Once ipaddress.ip_address() parses the string, the returned object exposes attributes like is_private, is_loopback, is_global, and is_reserved so you can classify the address by range. Boundstone's /v1/verify/ip endpoint does the same range classification, labeling each address as public, private, loopback, reserved, and similar categories, plus an is_public flag. That classification is pure arithmetic on the address itself, so it never involves looking up where the IP actually sits.

Does a valid IP address mean it's real, reachable, or tell me where it is?

No. A valid result only confirms the string is a properly formed IPv4 or IPv6 address and identifies its version and range type; it says nothing about whether the address is live, routable to a real host, or safe. It does not reveal geolocation, the owning network (ASN), whether it belongs to a hosting or datacenter provider, or whether it is a proxy, VPN, or Tor exit, since those require licensed IP-intelligence data. Boundstone makes this explicit: every IP response lists exactly what it checked (format, version, range classification) and what it did not, so a "valid" verdict is never mistaken for a reputation or location claim.

Thomas Tsui

Founder of Boundstone — building phone, email and IP validation you can actually verify.

One honest API for email, phone and IP — every response lists what it checked and what it didn't claim to. Free tier: 250 credits/month, no card, credits never expire.

More from Boundstone — API documentation · Benchmark methodology · The benchmark series · Buyer's checklist