← Docs and guides

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

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.