← Blog

Validate an IP address in PHP (filter_var, and when you need more)

PHP validates IP addresses without a single dependency — filter_var does format and private/reserved rejection natively; an API only earns its place past that line.

Contents

If you searched how to validate an IP address in PHP, the honest answer is that you almost certainly do not need a library, a package, or an API. PHP ships a built-in filter that checks IP format, and with a flag or two will also pin the version to IPv4 or IPv6 and reject private and reserved ranges — all in the standard library, no dependency, no network call. Most of the time that filter is the whole job. This post shows exactly how to use it, then draws a clear line at the point where a filter stops answering your question and a service starts.

When "validate IP address in PHP" just means filter_var

The core tool is filter_var with the FILTER_VALIDATE_IP filter. It returns the IP string on success and false on failure, so a strict comparison against false is the correct check.

<?php

function isValidIp(string $ip): bool
{
    return filter_var($ip, FILTER_VALIDATE_IP) !== false;
}

var_dump(isValidIp('8.8.8.8'));                    // bool(true)
var_dump(isValidIp('2606:4700:4700::1111'));       // bool(true)
var_dump(isValidIp('999.1.1.1'));                  // bool(false)
var_dump(isValidIp('not-an-ip'));                  // bool(false)

That is the answer for "is this a syntactically valid IP address." No Composer install, no wrapper. If format is all you care about, you can stop reading here — the standard library has you covered.

Pin the version: IPv4 or IPv6

By default FILTER_VALIDATE_IP accepts both families. When your schema expects one — an IPv4 column, an IPv6-only feature flag — pass a flag to pin the version. FILTER_FLAG_IPV4 accepts only v4, FILTER_FLAG_IPV6 only v6.

<?php

// Accept IPv4 only
filter_var('10.0.0.5', FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);   // "10.0.0.5"
filter_var('::1',      FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);   // false

// Accept IPv6 only
filter_var('::1',      FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);   // "::1"
filter_var('10.0.0.5', FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);   // false

Pinning the version at the boundary means a v6 string never sneaks into a v4-shaped field, and you fail fast instead of storing something your downstream code can't parse.

Reject private and reserved ranges natively

Here is the part people reach for a library to do, and shouldn't. filter_var will reject private and reserved ranges for you with two more flags: FILTER_FLAG_NO_PRIV_RANGE fails on private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and fc00::/7), and FILTER_FLAG_NO_RES_RANGE fails on reserved ranges (loopback, link-local, and similar). Combine them with a bitwise OR.

<?php

function isPublicIp(string $ip): bool
{
    return filter_var(
        $ip,
        FILTER_VALIDATE_IP,
        FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
    ) !== false;
}

var_dump(isPublicIp('8.8.8.8'));        // bool(true)  — public
var_dump(isPublicIp('192.168.1.1'));    // bool(false) — private
var_dump(isPublicIp('127.0.0.1'));      // bool(false) — reserved (loopback)
var_dump(isPublicIp('169.254.0.1'));    // bool(false) — reserved (link-local)

If you're accepting a client-supplied IP — a header like X-Forwarded-For, a webhook source, a form field — this one line filters out the addresses that should never appear as a public source. If the difference between those buckets is fuzzy to you, what is a private IP address walks through the ranges and why they exist. For a value you just want to paste and check without writing any PHP, the keyless IP validator tool runs the same classification in the browser.

What filter_var does not tell you

filter_var answers a boolean: valid or not, public or not. It does not tell you where the address is, who runs it, or whether it's a VPN, proxy, or Tor exit. That's the honest limit of format-and-range validation in any language — and it's worth stating plainly, because a "valid public IP" is not the same as a trustworthy one.

At Boundstone we don't paper over that gap. IP intelligence — geolocation, ASN, hosting/datacenter detection, proxy/VPN/Tor detection, reputation — needs licensed data, and it is not shipped. Every response says so in a machine-readable list rather than leaving you to guess.

Classification over HTTP, as one system

So where does an API earn its place past the standard library? Not by re-doing filter_var's job over the network — that would be slower for no gain. It earns it when you want the classification itself as structured data, returned by the same contract that validates your phone numbers and email addresses, so one integration covers all three. POST /v1/verify/ip returns valid, version, normalized, classification, is_public, and is_bogon.

<?php

$ch = curl_init('https://api.boundstone.io/v1/verify/ip');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer bs_live_YOUR_KEY',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode(['ip' => '8.8.8.8']),
]);

$res = json_decode(curl_exec($ch), true);
curl_close($ch);

echo $res['classification'];              // "public"
var_dump($res['is_public']);              // bool(true)
var_dump($res['is_bogon']);              // bool(false)

// The honesty contract — read it, don't assume:
print_r($res['checks']['performed']);
// ["format", "version", "range_classification"]
print_r($res['checks']['not_performed']);
// ["geolocation", "asn", "hosting_datacenter", "proxy_vpn_tor", "reputation"]

The checks.not_performed array is the point. It tells your code, every call, that a classification of "public" was reached by range math and nothing else — no geo, no VPN detection was consulted. That's what makes the "public" you get back trustworthy: you know precisely what it does and does not mean.

The short version

  • Just validating format? filter_var($ip, FILTER_VALIDATE_IP) — done, no dependency.
  • Need a specific family? Add FILTER_FLAG_IPV4 or FILTER_FLAG_IPV6.
  • Rejecting private/reserved source IPs? Add FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE. PHP does this natively — reach for it before any package.
  • Want public/private/bogon classification as structured data across phone, email, and IP in one contract? POST /v1/verify/ip, and read checks.not_performed.
  • Geolocation, VPN, or proxy detection? Not something filter_var can do — and at Boundstone it's honestly not_performed until licensed data ships.

Same story in another runtime? Here's how to validate an IP in Node.js. The docs cover the full response shape and the free tier: 250 credits a month, no card, credits never expire.

Frequently asked questions

How do you validate an IP address in PHP?

Use PHP's built-in filter_var function with the FILTER_VALIDATE_IP filter; it returns the address unchanged if the string is a syntactically valid IPv4 or IPv6, and false otherwise. You can narrow it with flags such as FILTER_FLAG_IPV4 or FILTER_FLAG_IPV6 to force a version. Keep in mind this validates the textual format only, so a passing result means the string is well-formed, not that any real host exists or is reachable at that address.

Does filter_var tell me an IP's location or whether it's a VPN or proxy?

No. filter_var only checks the address format and, with optional flags, whether it falls in a private or reserved range; it has no knowledge of geolocation, ASN, hosting or datacenter status, proxy/VPN/Tor use, or reputation. Those answers require a licensed dataset and a separate lookup. Boundstone's IP verification is explicit about the same boundary: it returns format, version, and range classification, and honestly lists geolocation, ASN, hosting/datacenter, proxy/VPN/Tor, and reputation as checks it does not perform.

How do I reject private or reserved IP addresses in PHP or via an API?

In PHP, pass FILTER_FLAG_NO_PRIV_RANGE and/or FILTER_FLAG_NO_RES_RANGE to filter_var with FILTER_VALIDATE_IP, and it will return false for addresses in those ranges. If you would rather get the classification back than a bare pass or fail, Boundstone's IP endpoint returns the version and a range classification such as public, private, loopback, or reserved, plus a flag for whether the address is publicly routable. That is useful for filtering signup or form data at scale, and it stops at classification: it does not geolocate the address or claim to detect a proxy or VPN.

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