Как получить IP-адрес клиента в PHP? Проблемы с HTTP_X_FORWARDED_FOR и REMOTE_ADDR
Правильное получение IP-адреса клиента в PHP
Вы правы — простое использование $_SERVER['REMOTE_ADDR'] не всегда дает реальный IP клиента, особенно когда сайт работает за прокси, CDN или балансировщиком нагрузки. Однако именно REMOTE_ADDR является единственным надежным источником — это IP адрес, с которого фактически пришло TCP-соединение к вашему серверу. Все остальные заголовки (X-Forwarded-For, X-Real-IP) могут быть подделаны злоумышленником.
Вот универсальная и безопасная функция для получения IP:
<?php
function getClientIP() {
$ip_keys = [
'HTTP_X_FORWARDED_FOR', // Стандартный прокси
'HTTP_X_REAL_IP', // Nginx proxy
'HTTP_CLIENT_IP', // Некоторые старые прокси
'HTTP_CF_CONNECTING_IP', // Cloudflare
'REMOTE_ADDR' // Последняя инстанция
];
foreach ($ip_keys as $key) {
if (isset($_SERVER[$key]) && !empty($_SERVER[$key])) {
// X-Forwarded-For может содержать список IP через запятую
$ip_list = explode(',', $_SERVER[$key]);
$ip = trim($ip_list[0]); // Берем первый (самый левый) IP
// Валидация: должен быть валидным IPv4 или IPv6
if (filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
return $ip;
}
}
}
// Если ничего не подошло — возвращаем REMOTE_ADDR
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}
// Использование
$user_ip = getClientIP();
echo "Ваш IP: " . htmlspecialchars($user_ip);
?>
Как это работает:
- Приоритет заголовков: Функция проверяет заголовки от наиболее специфичных (прокси/CDN) к базовому REMOTE_ADDR.
- Обработка X-Forwarded-For: Этот заголовок может содержать цепочку IP (например:
203.0.113.1, 198.51.100.2, 192.0.2.3). Первый IP — это реальный клиент, остальные — адреса прокси. - Валидация: Мы проверяем, что IP не является частным (10.x.x.x, 192.168.x.x, 172.16-31.x.x) и не из зарезервированного диапазона. Это защищает от подделки, когда злоумышленник пытается выдать себя за локальный адрес.
- Cloudflare: Специальный заголовок
HTTP_CF_CONNECTING_IPпредоставляется Cloudflare и содержит реальный IP клиента.
Важные замечания:
Если ваш сайт работает только за одним известным прокси (например, Cloudflare или nginx), можно упростить код:
<?php
// Для Cloudflare
if (isset($_SERVER['HTTP_CF_CONNECTING_IP'])) {
$user_ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
} else {
$user_ip = $_SERVER['REMOTE_ADDR'];
}
// Для nginx (если настроен proxy_set_header X-Real-IP $remote_addr;)
if (isset($_SERVER['HTTP_X_REAL_IP'])) {
$user_ip = $_SERVER['HTTP_X_REAL_IP'];
} else {
$user_ip = $_SERVER['REMOTE_ADDR'];
}
?>
Никогда не доверяйте слепо заголовкам X-Forwarded-For, если ваш сервер напрямую доступен из интернета — любой может отправить запрос с поддельным заголовком. Всегда используйте валидацию с FILTER_FLAG_NO_PRIV_RANGE и FILTER_FLAG_NO_RES_RANGE.
Альтернативный подход: использование библиотеки или middleware
Для более сложных проектов рекомендую использовать готовые решения, которые учитывают все нюансы:
<?php
// Пример с библиотекой 'stevebauman/location'
use Stevebauman\Location\Facades\Location;
$ip = request()->ip(); // Laravel helper
$position = Location::get($ip);
// Или используйте symfony/http-foundation
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
$ip = $request->getClientIp(); // Умный метод, который сам выбирает доверенные прокси
?>
Настройка доверенных прокси (Laravel пример)
В современных фреймворках (Laravel, Symfony) можно указать, каким прокси доверять. Laravel использует middleware TrustProxies:
// app/Http/Middleware/TrustProxies.php
protected $proxies = [
'173.245.48.0/20', // Cloudflare
'103.21.244.0/22', // Cloudflare
// или '*' для доверия всем прокси (только если сервер не в открытом доступе)
];
После этого request()->ip() будет автоматически использовать X-Forwarded-For только от доверенных источников, игнорируя подделки из интернета.
Вывод: Для простых проектов используйте функцию с валидацией из первого ответа. Для продакшена на фреймворке — настройте доверенные прокси и используйте встроенные методы.