Get a visitor's country from an IP address in PHP
One PHP function with the cURL extension returns the visitor's country, with short timeouts, an optional API key and an optional APCu cache.
1. A lookup function
Optionally create a free key on the home page and set it as the environment variable IPATLAS_KEY (for PHP-FPM, in the pool configuration with env[IPATLAS_KEY] = ...). Without a key the function still works, within the keyless limit. Save this as geo.php:
<?php
// geo.php: country_of("8.8.8.8") returns ["code" => "US", "name" => "United States"] or null.
function country_of(string $ip): ?array {
// Only public addresses have a location; this also rejects anything that is not an IP.
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return null;
}
// Optional cache: APCu keeps answers between requests (install it with your PHP packages).
$cacheKey = "ipatlas:" . $ip;
$useCache = function_exists("apcu_fetch");
if ($useCache) {
$hit = apcu_fetch($cacheKey, $found);
if ($found) return $hit ?: null; // false means "looked up, no answer"
}
$key = getenv("IPATLAS_KEY") ?: null; // optional; keyless is limited to 1,000 requests/day/IP
$ch = curl_init("https://api.ipatlas.dev/v1/" . rawurlencode($ip));
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 1,
CURLOPT_TIMEOUT => 2, // never hold up your own page
CURLOPT_HTTPHEADER => $key ? ["X-API-Key: $key"] : [],
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$data = ($body !== false && $status === 200) ? json_decode($body, true) : null; // 429 and 401 end up as null
$result = (is_array($data) && !empty($data["country_code"]))
? ["code" => $data["country_code"], "name" => $data["country"] ?? null]
: null;
if ($useCache) apcu_store($cacheKey, $result ?? false, $result ? 6 * 3600 : 60);
return $result;
}
2. Use it in a page
<?php
require __DIR__ . "/geo.php";
$country = country_of($_SERVER["REMOTE_ADDR"] ?? "");
echo "Hello from " . htmlspecialchars($country["name"] ?? "somewhere");
Try it from the command line first: php -r 'require "geo.php"; print_r(country_of("8.8.8.8"));' prints the code US and the name United States. The CLI has no APCu cache by default, so every call goes to the API there.
REMOTE_ADDR is the address that connected to PHP. Behind a load balancer or CDN it is the proxy's address; then read the visitor address from the header your proxy sets (for example X-Forwarded-For or CF-Connecting-IP), but only when the request really came through that proxy, because a visitor can send any value in these headers.
Notes
- Without APCu every page view makes one API call. With it, repeat visitors cost nothing for six hours, which matters for the 50,000 monthly lookups of the free key.
- The function returns
nullon a timeout, a429or an unknown address, so decide on a default (for example your main language or currency). - Do not log full visitor addresses together with locations unless you need to; see our privacy page for what we store.
Limits: without a key, 1,000 requests per day per IP address (10 req/s); with a free key, 50,000 requests per month (50 req/s). Over a limit the API returns 429. IP geolocation is approximate: country is usually right, city often is not.
Attribution
The data is DB-IP Lite, licensed CC BY 4.0. If you show or use it in a public product, link to DB-IP, for example in your footer:
<a href="https://db-ip.com">IP Geolocation by DB-IP</a>
See the API docs for fields, limits and error codes.