How to Get IP Geolocation in PHP (API Tutorial)

By Mudassar Tariq Software Engineer at IP Geolocation
Posted on June 22, 2026 | 10 min read
How to Get IP Geolocation in PHP (API Tutorial)
Back to All Tutorials

IP geolocation in PHP turns a visitor's IP address into location, timezone, currency, and network data you can use for localization, fraud checks, access control, and analytics. PHP has no built-in, current way to get the location from an IP address, so you call a geolocation API and read the JSON it returns.

This tutorial uses the IPGeolocation API and its official PHP SDK. You will install the library with Composer, run your first lookup, read the real visitor's IP in both vanilla PHP and Laravel, then add VPN, abuse, and bulk lookups when you need them.


TL;DR

  • Get IP geolocation: install ipgeolocation/ipgeolocation-php-sdk with Composer (PHP 8.1+), create an IpGeolocationClient with your API key, call lookupIpGeolocation(['ip' => '8.8.8.8']) (or omit the IP for the caller's own address), and read the location, time_zone, currency, and asn objects off $response->data.
  • Visitor IP: in a web app, resolve the real client IP from X-Forwarded-For (vanilla PHP) or $request->ip() with trusted proxies (Laravel).
  • Free vs paid: the core lookup is free; VPN/proxy detection, abuse contact, and bulk are paid.
  • No SDK: call /v3/ipgeo with cURL and json_decode.
  • IPv6: works the same as IPv4, on every plan.

What you need

  • PHP 8.1 or newer with Composer.
  • A free IPGeolocation.io API key (next section).
  • For the framework examples, a Laravel app. The vanilla PHP examples run anywhere, including shared hosting.

Get your free API key

Create an account and copy a key from your dashboard. The free tier is 1,000 lookups a day with no credit card and covers the core of this tutorial: location, country metadata, currency, basic ASN, and time zone. Paid plans raise the limits and add more data, including the optional security, abuse contact, and bulk lookups shown later in this guide. You can compare tiers on the pricing page.

Keep the key in an environment variable, never in your source. The examples read it from IPGEOLOCATION_API_KEY.


Install the PHP SDK

Install the official IPGeolocation.io PHP library:

composer require ipgeolocation/ipgeolocation-php-sdk

First IP lookup

Then run a single lookup. The client is built on Guzzle and returns typed objects, so you get IDE autocompletion on the response:

<?php

require __DIR__ . '/vendor/autoload.php';

use Ipgeolocation\Sdk\IpGeolocationClient;

$client = new IpGeolocationClient([
    'api_key' => getenv('IPGEOLOCATION_API_KEY'),
]);

try {
    $response = $client->lookupIpGeolocation(['ip' => '8.8.8.8']);

    $data = $response->data;
    echo $data->ip . PHP_EOL;                              // 8.8.8.8
    echo ($data->location?->country_name ?? '') . PHP_EOL; // United States
    echo ($data->location?->city ?? '') . PHP_EOL;         // Mountain View
    echo ($data->time_zone?->name ?? '') . PHP_EOL;        // America/Los_Angeles
} finally {
    $client->close();
}

Two things worth knowing. The ?-> null-safe operator matters because optional objects can be absent depending on your plan and request, and reading a property off null would otherwise fatal. And the client holds an open Guzzle connection, so call close() in a finally block. You can pass a plain array, as above, or a typed LookupIpGeolocationRequest if you want validation before the request is sent.

The short SDK snippets below are fragments: they assume an open $client and the use imports from this section, the code that goes between creating the client and $client->close() above. Run each inside that try block, or give the snippet its own client the way the production and Laravel examples do. The cURL example stands alone.


1. Look up the caller's IP

Leave out ip and the API geolocates the IP making the request. That is your server's own egress IP, handy for testing or a quick check, not the website visitor's IP. For the visitor, see the next section.

$response = $client->lookupIpGeolocation();
echo $response->data->ip . PHP_EOL;

Get the real visitor IP in PHP

In a real app you want the visitor's IP, and that is where most tutorials get it wrong. $_SERVER['REMOTE_ADDR'] holds the IP of whatever connected to your server. Behind a load balancer or CDN that is the proxy, not the user. The real client IP is in the X-Forwarded-For header, which is a comma-separated list (client, then each proxy).

X-Forwarded-For is also trivial to spoof on a direct request, so the helper below only reads it when the direct connection is a proxy you list as trusted, and validates every value. Everything else falls back to REMOTE_ADDR.

How a PHP app reads the real visitor IP behind a proxy using X-Forwarded-For
<?php

require __DIR__ . '/vendor/autoload.php';

use Ipgeolocation\Sdk\IpGeolocationClient;

/**
 * Resolve the visitor IP (simplified, exact-match example). X-Forwarded-For is
 * only read when the direct connection (REMOTE_ADDR) is a proxy you trust,
 * because the header can be spoofed on a direct request. Pass your load balancer
 * or CDN IPs as trusted; for CDN ranges, match CIDRs or use framework middleware.
 *
 * @param string[] $trustedProxies
 */
function visitorIp(array $trustedProxies = []): ?string
{
    $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? null;

    if (
        $remoteAddr !== null
        && in_array($remoteAddr, $trustedProxies, true)
        && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])
    ) {
        // X-Forwarded-For is "client, proxy1, proxy2". Walk right to left, skip
        // your own proxies, and take the first real public address.
        $chain = array_map('trim', explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
        foreach (array_reverse($chain) as $candidate) {
            if (in_array($candidate, $trustedProxies, true)) {
                continue;
            }
            if (filter_var($candidate, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                return $candidate;
            }
        }
    }

    // No trusted proxy in front: the only address you can trust is the peer.
    return ($remoteAddr !== null && filter_var($remoteAddr, FILTER_VALIDATE_IP)) ? $remoteAddr : null;
}

// Empty list trusts only REMOTE_ADDR. Add your load balancer or CDN IPs to read X-Forwarded-For.
$ip = visitorIp(['10.0.0.1']);
if ($ip !== null) {
    $client = new IpGeolocationClient(['api_key' => getenv('IPGEOLOCATION_API_KEY')]);
    try {
        $response = $client->lookupIpGeolocation(['ip' => $ip]);
        echo ($response->data->location?->country_name ?? 'Unknown') . PHP_EOL;
    } finally {
        $client->close();
    }
}

For CDN or load-balancer ranges (Cloudflare, AWS, and similar), match against their published IP ranges (CIDR) or use your framework's trusted-proxy middleware instead of hard-coding single IPs. For which entry to trust and how to set this up behind Nginx, Cloudflare, and other proxies, see our guide to getting the real client IP behind a proxy.


Laravel example

Laravel already solves the proxy problem if you configure it. Set your load balancer in the TrustProxies middleware (in bootstrap/app.php on Laravel 11, or app/Http/Middleware/TrustProxies.php on older versions). Then $request->ip() returns the resolved client IP and you do not parse headers yourself:

use Illuminate\Http\Request;
use Ipgeolocation\Sdk\IpGeolocationClient;

Route::get('/where', function (Request $request) {
    $client = new IpGeolocationClient([
        'api_key' => getenv('IPGEOLOCATION_API_KEY'),
    ]);

    try {
        $response = $client->lookupIpGeolocation(['ip' => $request->ip()]);

        return [
            'country'  => $response->data->location?->country_name,
            'city'     => $response->data->location?->city,
            'currency' => $response->data->currency?->code,
        ];
    } finally {
        $client->close();
    }
});

In production you would bind the client once in a service provider rather than newing it per request, but this keeps the example self-contained.


Read the API response

A default GeoIP lookup returns more than most apps use. The PHP geolocation API hands this data back as typed objects; this trimmed example shows what the free plan returns:

{
  "ip": "8.8.8.8",
  "location": {
    "country_name": "United States",
    "state_prov": "California",
    "city": "Mountain View",
    "zipcode": "94043-1351",
    "latitude": "37.42240",
    "longitude": "-122.08421"
  },
  "country_metadata": { "calling_code": "+1", "tld": ".us", "languages": ["en-US", "es-US", "haw", "fr"] },
  "currency": { "code": "USD", "name": "US Dollar", "symbol": "$" },
  "asn": { "as_number": "AS15169", "organization": "Google LLC", "country": "US" },
  "time_zone": { "name": "America/Los_Angeles" }
}
PHP SDK response objects from an IP geolocation lookup, with paid modules marked

In the SDK these are typed objects on $response->data, so $response->data->currency?->code gives USD. The asn object identifies the network the IP belongs to; if you want the background on what an ASN is, see our guide on how an ASN differs from an ISP.

On a paid plan, the same lookup returns more: full asn detail plus company and network objects, with security and abuse available through include. The SDK docs cover what each plan returns.

If you only need a field or two, ask for them and the response stays small:

$response = $client->lookupIpGeolocation([
    'ip'     => '8.8.8.8',
    'fields' => ['location.country_name', 'location.city', 'time_zone.name'],
]);

echo $response->data->location?->country_name . PHP_EOL;

fields is one of several request options (alongside excludes, lang, output, and custom headers); the full list and the response metadata schema live in the SDK docs under request options and response metadata. Both IPv4 and IPv6 work on every plan.


IP geolocation in PHP without the SDK (raw cURL)

If you only need one lookup in a script you will run once, raw cURL is honestly fine. Reach for the SDK when this code has to keep working in production. This version sets timeouts, checks the status code, and guards json_decode:

<?php

$apiKey = getenv('IPGEOLOCATION_API_KEY');

// Note: the key travels in the query string, so don't log the full URL.
$url = 'https://api.ipgeolocation.io/v3/ipgeo?' . http_build_query([
    'ip'     => '8.8.8.8',
    'apiKey' => $apiKey,
    'fields' => 'location.country_name,location.city,time_zone.name',
]);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT        => 10,
]);

$body = curl_exec($ch);

if ($body === false) {
    $error = curl_error($ch);
    throw new RuntimeException("IP geolocation request failed: {$error}");
}

$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
// PHP 8 frees the handle when $ch goes out of scope, so curl_close() is not needed.

if ($status !== 200) {
    throw new RuntimeException("IP geolocation API returned HTTP {$status}");
}

$data = json_decode($body, true);

if (!is_array($data)) {
    throw new RuntimeException('Could not parse the IP geolocation response.');
}

echo ($data['location']['country_name'] ?? 'Unknown') . PHP_EOL;
echo ($data['location']['city'] ?? 'Unknown') . PHP_EOL;
echo ($data['time_zone']['name'] ?? 'Unknown') . PHP_EOL;

Handle errors in production

The SDK throws typed exceptions, which makes it easy to treat a bad key differently from a rate limit or a timeout. It does not retry for you, so add a small backoff around transient failures and fail fast on the ones a retry will not fix:

<?php

require __DIR__ . '/vendor/autoload.php';

use Ipgeolocation\Sdk\IpGeolocationClient;
use Ipgeolocation\Sdk\RateLimitException;
use Ipgeolocation\Sdk\ServerErrorException;
use Ipgeolocation\Sdk\RequestTimeoutException;
use Ipgeolocation\Sdk\TransportException;
use Ipgeolocation\Sdk\UnauthorizedException;
use Ipgeolocation\Sdk\ApiException;

$client = new IpGeolocationClient([
    'api_key'         => getenv('IPGEOLOCATION_API_KEY'),
    'connect_timeout' => 5,
    'read_timeout'    => 10,
]);

function lookupWithRetry(IpGeolocationClient $client, array $request, int $maxAttempts = 3): ?object
{
    for ($attempt = 1; ; $attempt++) {
        try {
            return $client->lookupIpGeolocation($request);
        } catch (RateLimitException | ServerErrorException | RequestTimeoutException | TransportException $e) {
            if ($attempt >= $maxAttempts) {
                error_log("IP geolocation failed after {$attempt} attempts: " . $e->getMessage());
                return null;
            }
            usleep((2 ** $attempt) * 250000); // 0.5s, then 1s
        } catch (UnauthorizedException $e) {
            error_log('IP geolocation auth failed, check IPGEOLOCATION_API_KEY: ' . $e->getMessage());
            return null; // a retry will not fix a bad key
        } catch (ApiException $e) {
            error_log('IP geolocation API error ' . ($e->status_code ?? '') . ': ' . $e->getMessage());
            return null;
        }
    }
}

try {
    $response = lookupWithRetry($client, ['ip' => '8.8.8.8']);
    if ($response !== null) {
        echo ($response->data->location?->country_name ?? 'Unknown') . PHP_EOL;
    }
} finally {
    $client->close();
}

Set connect_timeout and read_timeout together so a slow network never hangs a request worker.


Common mistakes to avoid

A few things trip up most PHP geolocation code once it hits production:

  • Trusting X-Forwarded-For blindly. Read it only behind a proxy you control, with CIDR matching or your framework's trusted-proxy middleware for CDN ranges; otherwise the header is spoofable.
  • Geolocating REMOTE_ADDR behind a CDN. That returns the CDN's location, not the visitor's. Resolve the real client IP first.
  • Hard-coding the API key. Keep it in an environment variable, never in source or client-side code.
  • Sending private or reserved IPs. Validate the address first; a private IP has no public location.
  • Fetching the whole response every time. Use fields to keep a PHP IP lookup lean when you only need the country or city, and cache results per IP to save credits and latency.
  • Treating IP geolocation as exact. It is approximate, especially on mobile and VPN traffic, so do not make it your only fraud or access-control signal.

Optional paid modules: security, abuse, bulk

The same client reaches the rest of the API when you need it. These are paid features, so each one links to its full reference instead of repeating it here:

  • VPN, proxy, and Tor detection: add include => ['security'] for a security object with threat_score, is_vpn, is_proxy, and is_tor (2 credits per lookup). See the IP Security API.
  • Network abuse contact: add include => ['abuse'] for the reporting contact behind an IP range (1 credit). See the Abuse Contact API.
  • Bulk lookups: call bulkLookupIpGeolocation(['ips' => [...]]) to handle up to 50,000 IPs in one request. See the SDK bulk reference.

FAQ

Install the official SDK with composer require ipgeolocation/ipgeolocation-php-sdk, create an IpGeolocationClient with your API key, call lookupIpGeolocation(['ip' => '8.8.8.8']), and read location, time_zone, currency, and asn off $response->data. The free tier allows 1,000 lookups a day.

Use $request->ip() after configuring the TrustProxies middleware with your load balancer or CDN. Laravel then reads X-Forwarded-For only from infrastructure you trust and returns the resolved client IP, instead of the proxy address you would get from REMOTE_ADDR.

Yes. The free tier gives 1,000 lookups a day with no credit card and returns location, country metadata, currency, basic ASN, and time zone for any IPv4 or IPv6 address. VPN/proxy detection, abuse contact, and bulk lookups are paid.

Request only what you need with fields => ['location.country_name'] (or the country code via location.country_code2) and read $response->data->location->country_name. Filtering fields keeps the payload small and is the cleanest way to answer "get country from IP" in PHP.

Yes. Pass an IPv6 address as ip exactly as you would an IPv4 one. Both are supported on every plan, including the free tier, and the response shape is identical.

The SDK gives typed responses, request validation, and named exceptions you can branch on, which is what you want in production. Raw cURL works with zero dependencies and is fine for a quick test or a constrained environment. Use the SDK when the code has to keep running.


Where to go next

Bind the client in a service provider, cache results per IP to save credits, and add include modules as your needs grow. From here:

Related Tutorials

How to Get IP Geolocation in JavaScript (Node + Browser)
How to Get IP Geolocation in JavaScript (Node + Browser)

Get IP geolocation in JavaScript the right way: the official SDK in Node.js, the correct browser pattern without exposing your key, the visitor's real IP via Express and Next.js, and the location, timezone, currency, ASN, VPN, proxy and threat data the API returns.

Posted onJune24, 2026
ByMudassar Tariq
Read More
How to Get IP Geolocation in Python (API Tutorial)
How to Get IP Geolocation in Python (API Tutorial)

Get IP geolocation in Python with the official IPGeolocation.io library: single lookups, async requests, and real visitor IP handling in Flask and FastAPI, plus optional bulk lookup and VPN/proxy detection examples.

Posted onJune19, 2026
ByMudassar Tariq
Read More
WooCommerce Fraud Prevention: Setup Guide for FraudShield
WooCommerce Fraud Prevention: Setup Guide for FraudShield

A full setup walkthrough for FraudShield, the WooCommerce fraud detection plugin built on the ipgeolocation.io API. Install to first protected order in under 20 minutes.

Posted onJune16, 2026
Read More
How to Secure Your API Key Before Production
How to Secure Your API Key Before Production

Your API key works in development, but shipping it as-is to production is a security risk. This tutorial covers environment variables, backend proxies, CORS-based auth, and usage monitoring for the IPGeolocation.io API.

Posted onMay14, 2026
Read More
IP Geolocation in Google Tag Manager
IP Geolocation in Google Tag Manager

Learn how to set up IP geolocation tracking in Google Tag Manager using the official IPGeolocation.io custom template. Covers API key setup, tag configuration, data layer variables, and practical use cases.

Posted onMay7, 2026
Read More
How to Use the IPGeolocation.io Astronomy API
How to Use the IPGeolocation.io Astronomy API

Learn how to use the IPGeolocation.io Astronomy API to get sunrise, sunset, moon phase, golden hour, and 50+ other data fields from a single API call.

Posted onMay6, 2026
Read More

Subscribe to Our Newsletter

Get the latest in geolocation tech, straight to your inbox.