When building SaaS applications, it is common to treat internal (private or reserved) IP addresses differently from public ones. Typical examples are rate limiting, audit logging, or skipping geo-IP lookups for localhost traffic.
This post shows how to check whether an IP address is internal, starting from an Elixir example and then translating the same idea to PHP.
The Elixir way
In Elixir, the standard library provides :inet.parse_address/1 to validate IP addresses. From there, you can pattern match on the octets to exclude private and reserved ranges.
defmodule IPUtils do
def public_ipv4?(ip) when is_binary(ip) do
case :inet.parse_address(String.to_charlist(ip)) do
{:ok, {a, b, _c, _d}} ->
not private_or_reserved?(a, b)
_ ->
false
end
end
defp private_or_reserved?(10, _), do: true
defp private_or_reserved?(127, _), do: true
defp private_or_reserved?(192, 168), do: true
defp private_or_reserved?(172, b) when b >= 16 and b <= 31, do: true
defp private_or_reserved?(_, _), do: false
end
IPUtils.public_ipv4?("127.0.0.1") # false
IPUtils.public_ipv4?("8.8.8.8") # true
Pattern matching keeps the intent clear and makes it easy to extend this logic later if you want to support IPv6 or additional ranges.
The PHP approach
PHP ships with a very convenient helper: filter_var. Combined with the right flags, it allows you to validate only public IPv4 addresses.
$user_ip = '127.0.0.1';
$is_public = filter_var($user_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if ($is_public === false) {
// Internal, private, or reserved IP
} else {
// Public IPv4 address
}
What this does:
FILTER_VALIDATE_IPchecks that the value is a valid IP address.FILTER_FLAG_IPV4restricts the check to IPv4.FILTER_FLAG_NO_PRIV_RANGEexcludes private ranges such as10.0.0.0/8and192.168.0.0/16.FILTER_FLAG_NO_RES_RANGEexcludes reserved ranges like127.0.0.0/8.
If the function returns false, the IP is either invalid or internal/reserved.
Closing thoughts
PHP’s filter_var is hard to beat for conciseness, but the same idea translates cleanly to other ecosystems:
- Validate the IP address first.
- Explicitly exclude private and reserved ranges.
- Treat everything else as public.
Keeping this logic centralized (for example in a small utility module) helps ensure consistent behavior across your application, regardless of the language you are using.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.