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
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';
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;
}
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
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
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';
}
}
Register in app/Http/Kernel.php:
protected $middlewareGroups = [
'web' => [
// ...
\App\Http\Middleware\DetectCountry::class,
],
];
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,
]);
}
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>';
}
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);
}
Add to cron:
*/5 * * * * /usr/bin/php /usr/local/bin/ddns_updater.php >> /var/log/ddns.log 2>&1
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;
}
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)