DEV Community

Vix
Vix

Posted on

Getting the public IP in PHP — no dependencies, no API key

Getting the public IP in PHP — no dependencies, no API key

PHP is still one of the most widely deployed server-side languages, running a significant share of the web's backend code. If you're building a PHP application that needs the public IP address — for geolocation, DDNS, diagnostics, or country detection — this article covers the common patterns using IPPubblico.org: free, no key, HTTPS, JSON and plain text endpoints.


Use case 1 — Your server's own public IP (one-liner)

The simplest case: a PHP script that needs to know its own public IP.

<?php
$ip = trim(file_get_contents('https://ipv4.ippubblico.org/'));
echo $ip; // 203.0.113.42
Enter fullscreen mode Exit fullscreen mode

file_get_contents works if allow_url_fopen is enabled (it is by default on most servers). If not, use cURL (see below).


Use case 2 — With cURL (recommended for production)

file_get_contents has no timeout control and minimal error handling. For production code, cURL is better:

<?php
function getPublicIP(): ?string {
    $ch = curl_init('https://ipv4.ippubblico.org/');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 5,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($response === false || $httpCode !== 200) {
        return null;
    }
    return trim($response);
}

$ip = getPublicIP();
echo $ip ?? 'Unavailable';
Enter fullscreen mode Exit fullscreen mode

Use case 3 — Full geolocation data

When you need country, city, ISP and timezone in addition to the IP:

<?php
function getIPInfo(?string $ip = null): ?array {
    $url = 'https://ippubblico.org/?api=1';
    if ($ip !== null) {
        $url .= '&ip=' . urlencode($ip);
    }

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 5,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($response === false || $httpCode !== 200) {
        return null;
    }

    $data = json_decode($response, true);
    if (!$data || $data['status'] !== 'ok') {
        return null;
    }
    return $data;
}

$info = getIPInfo();
if ($info) {
    echo 'IP:       ' . $info['ip'] . PHP_EOL;
    echo 'Country:  ' . $info['geo']['country'] . PHP_EOL;
    echo 'City:     ' . $info['geo']['city'] . PHP_EOL;
    echo 'ISP:      ' . $info['isp'] . PHP_EOL;
    echo 'Timezone: ' . $info['timezone'] . PHP_EOL;
}
Enter fullscreen mode Exit fullscreen mode

Use case 4 — Multilingual response

One of IPPubblico's distinct features: city, region and country names in the user's language. Add ?lang=XX:

<?php
function getIPInfoLocalized(string $lang = 'en'): ?array {
    $url = 'https://ippubblico.org/?api=1&lang=' . urlencode($lang);

    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 5,
        CURLOPT_SSL_VERIFYPEER => true,
    ]);
    $response = curl_exec($ch);
    curl_close($ch);

    $data = json_decode($response, true);
    return ($data && $data['status'] === 'ok') ? $data : null;
}

// Japanese response
$info = getIPInfoLocalized('ja');
echo $info['geo']['country']; // 日本
echo $info['geo']['city'];    // 東京

// Portuguese response
$info = getIPInfoLocalized('pt');
echo $info['geo']['country']; // Japão
echo $info['geo']['city'];    // Tóquio
Enter fullscreen mode Exit fullscreen mode

Supported localized languages: de, es, fr, ja, pt, ru, zh.


Use case 5 — Client IP geolocation in a web application

Detecting where your visitor is connecting from — the most common PHP web use case:

<?php
function getClientIP(): string {
    // Handle proxies and load balancers
    $headers = [
        'HTTP_CF_CONNECTING_IP',    // Cloudflare
        'HTTP_X_FORWARDED_FOR',     // Standard proxy header
        'HTTP_X_REAL_IP',           // Nginx
        'HTTP_X_FORWARDED',
        'REMOTE_ADDR',              // Direct connection fallback
    ];

    foreach ($headers as $header) {
        if (!empty($_SERVER[$header])) {
            $ip = trim(explode(',', $_SERVER[$header])[0]);
            if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                return $ip;
            }
        }
    }
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

function getClientIPInfo(): ?array {
    $clientIP = getClientIP();
    return getIPInfo($clientIP);
}

// Usage in a web request
$info = getClientIPInfo();
$country = $info['geo']['country_code'] ?? 'US'; // fallback to US
Enter fullscreen mode Exit fullscreen mode

Use case 6 — Country-based content in Laravel

A Laravel middleware that attaches country information to every request:

<?php
// app/Http/Middleware/DetectCountry.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;

class DetectCountry
{
    public function handle(Request $request, Closure $next)
    {
        $ip = $request->ip();

        // Cache by IP for 1 hour to avoid repeated API calls
        $countryCode = Cache::remember("country_{$ip}", 3600, function () use ($ip) {
            return $this->detectCountry($ip);
        });

        $request->attributes->set('country_code', $countryCode);

        return $next($request);
    }

    private function detectCountry(string $ip): string
    {
        $ch = curl_init("https://ippubblico.org/?api=1&ip=" . urlencode($ip));
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 3,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);
        $response = curl_exec($ch);
        curl_close($ch);

        $data = json_decode($response, true);
        return $data['geo']['country_code'] ?? 'US';
    }
}
Enter fullscreen mode Exit fullscreen mode

Register in app/Http/Kernel.php:

protected $middlewareGroups = [
    'web' => [
        // ...
        \App\Http\Middleware\DetectCountry::class,
    ],
];
Enter fullscreen mode Exit fullscreen mode

Use in any controller:

public function index(Request $request)
{
    $country = $request->attributes->get('country_code', 'US');

    return view('home', [
        'currency' => $this->getCurrencyForCountry($country),
        'country'  => $country,
    ]);
}
Enter fullscreen mode Exit fullscreen mode

Use case 7 — WordPress plugin snippet

Detecting country in WordPress without a plugin or paid API:

<?php
function ipp_get_visitor_country(): string {
    $ip = $_SERVER['REMOTE_ADDR'] ?? '';

    // Check transient cache first (24 hours)
    $cacheKey = 'ipp_country_' . md5($ip);
    $cached    = get_transient($cacheKey);
    if ($cached !== false) {
        return $cached;
    }

    $response = wp_remote_get(
        'https://ippubblico.org/?api=1&ip=' . urlencode($ip),
        ['timeout' => 5, 'sslverify' => true]
    );

    if (is_wp_error($response)) {
        return 'US'; // fallback
    }

    $data = json_decode(wp_remote_retrieve_body($response), true);
    $country = $data['geo']['country_code'] ?? 'US';

    set_transient($cacheKey, $country, DAY_IN_SECONDS);
    return $country;
}

// Usage in any template
$country = ipp_get_visitor_country();
if ($country === 'IT') {
    echo '<p>Benvenuto! Visita il nostro negozio italiano.</p>';
}
Enter fullscreen mode Exit fullscreen mode

Use case 8 — DDNS updater script (CLI)

A PHP CLI script that checks if the server's public IP changed and updates a Cloudflare DNS record:

<?php
// Run with: php ddns_updater.php

define('CF_API_TOKEN',  getenv('CF_API_TOKEN'));
define('CF_ZONE_ID',    getenv('CF_ZONE_ID'));
define('CF_RECORD_ID',  getenv('CF_RECORD_ID'));
define('RECORD_NAME',   'home.yourdomain.com');
define('CACHE_FILE',    '/tmp/ddns_last_ip.txt');

function getCurrentIP(): ?string {
    $ch = curl_init('https://ipv4.ippubblico.org/');
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 10,
    ]);
    $ip = trim(curl_exec($ch));
    $ok = curl_getinfo($ch, CURLINFO_HTTP_CODE) === 200;
    curl_close($ch);
    return $ok ? $ip : null;
}

function getLastKnownIP(): ?string {
    return file_exists(CACHE_FILE) ? trim(file_get_contents(CACHE_FILE)) : null;
}

function updateCloudflare(string $ip): bool {
    $payload = json_encode([
        'type'    => 'A',
        'name'    => RECORD_NAME,
        'content' => $ip,
        'ttl'     => 60,
    ]);

    $ch = curl_init(sprintf(
        'https://api.cloudflare.com/client/v4/zones/%s/dns_records/%s',
        CF_ZONE_ID, CF_RECORD_ID
    ));
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST  => 'PUT',
        CURLOPT_POSTFIELDS     => $payload,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . CF_API_TOKEN,
            'Content-Type: application/json',
        ],
        CURLOPT_TIMEOUT        => 10,
    ]);
    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);

    return $response['success'] ?? false;
}

// Main
$current = getCurrentIP();
if ($current === null) {
    echo '[ERROR] Failed to get public IP' . PHP_EOL;
    exit(1);
}

$last = getLastKnownIP();

if ($current === $last) {
    echo "[OK] IP unchanged: {$current}" . PHP_EOL;
    exit(0);
}

echo "[INFO] IP changed: {$last}{$current}" . PHP_EOL;

if (updateCloudflare($current)) {
    file_put_contents(CACHE_FILE, $current);
    echo "[OK] DNS updated to {$current}" . PHP_EOL;
} else {
    echo '[ERROR] DNS update failed' . PHP_EOL;
    exit(1);
}
Enter fullscreen mode Exit fullscreen mode

Add to cron:

*/5 * * * * /usr/bin/php /usr/local/bin/ddns_updater.php >> /var/log/ddns.log 2>&1
Enter fullscreen mode Exit fullscreen mode

Handling rate limits

The API returns 429 Too Many Requests with a Retry-After header if the same IP sends too many requests in a short period:

<?php
function getPublicIPWithRetry(int $maxRetries = 2): ?string {
    for ($attempt = 0; $attempt <= $maxRetries; $attempt++) {
        $ch = curl_init('https://ipv4.ippubblico.org/');
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 5,
            CURLOPT_HEADER         => true,
        ]);
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
        curl_close($ch);

        if ($httpCode === 200) {
            return trim(substr($response, $headerSize));
        }

        if ($httpCode === 429 && $attempt < $maxRetries) {
            $headers = substr($response, 0, $headerSize);
            preg_match('/Retry-After:\s*(\d+)/i', $headers, $matches);
            $wait = (int)($matches[1] ?? 10);
            sleep($wait);
            continue;
        }

        break;
    }
    return null;
}
Enter fullscreen mode Exit fullscreen mode

Quick reference

Need Endpoint Response
IPv4 only https://ipv4.ippubblico.org/ 203.0.113.42
IPv6 only https://ipv6.ippubblico.org/ 2001:db8::1 or NONE
Both protocols https://ippubblico.org/?text=1 IPv4: x\nIPv6: x
Full geolocation https://ippubblico.org/?api=1 JSON with country, city, ISP
Localized geo https://ippubblico.org/?api=1&lang=ja City/country in Japanese

Full documentation: ippubblico.org/docs.html
Code examples: github.com/ippubblico/examples


Conclusion

PHP's cURL extension covers every use case cleanly — plain text for scripts and DDNS tools, JSON for web applications, and all the caching and retry logic you need in a few dozen lines. The Laravel middleware and WordPress snippets are production-ready starting points that cache results correctly to avoid hitting the API on every request.


Using IPPubblico in a PHP project? Share your use case in the comments.

Top comments (0)