Введение: почему «просто REMOTE_ADDR» уже не работает
Каждый PHP-разработчик хотя бы раз задавался вопросом: «Как получить IP адрес клиента?». На Stack Overflow эта тема собирает сотни тысяч просмотров, а под каноническим ответом — десятки уточняющих комментариев. Проблема в том, что в 2025 году простого $_SERVER['REMOTE_ADDR'] уже недостаточно.
Современные сайты живут за CDN (Cloudflare, Akamai), балансировщиками нагрузки и прокси-серверами. Пользователи сидят через VPN или Tor. А злоумышленники научились подделывать заголовки. Если вы хотите узнать настоящий IP-адрес посетителя — и при этом не открыть дверь для атак — нужно применять проверенные практики.
В этой статье я покажу, как работает определение IP в PHP, разберу подводные камни и дам готовый код, который можно смело использовать в 2025 году.
Как PHP «видит» IP-адрес?
Когда браузер отправляет запрос на сервер, PHP автоматически заполняет суперглобальный массив $_SERVER. За IP-адрес «собеседника» отвечает ключ REMOTE_ADDR. В идеальном мире (без прокси) это и есть IP клиента:
<?php
$ip = $_SERVER['REMOTE_ADDR']; // 192.168.1.5
?>
Но в реальности за сервером часто стоит nginx, HAProxy или Cloudflare. Тогда REMOTE_ADDR показывает IP прокси, а не пользователя. Подлинный адрес передаётся в HTTP-заголовках:
HTTP_X_FORWARDED_FOR— стандартный заголовок для прокси;HTTP_X_REAL_IP— используется nginx;HTTP_CF_CONNECTING_IP— специфичный для Cloudflare;HTTP_CLIENT_IP— редко, но встречается.
Проблема: любой из этих заголовков может быть подделан. Если вы доверитесь $_SERVER['HTTP_X_FORWARDED_FOR'] без проверки, злоумышленник легко подставит чужой IP.
REMOTE_ADDR против X-Forwarded-For: когда что использовать?
Вот ключевое правило, которое спасёт вас от головной боли:
- Если сайт стоит напрямую на сервере без прокси — используйте только
REMOTE_ADDR. - Если сайт за доверенным прокси (CDN, балансировщик) — берите IP из заголовка, но только после проверки, что запрос пришёл именно с вашего прокси.
В 2025 году стандарт де-факто — это X-Forwarded-For. Внутри него может быть цепочка IP: client, proxy1, proxy2. Первый адрес — клиентский, последний — адрес последнего прокси.
Почему нельзя слепо верить X-Forwarded-For?
Представьте: злоумышленник шлёт запрос с заголовком X-Forwarded-For: 127.0.0.1. Если ваш код просто читает $_SERVER['HTTP_X_FORWARDED_FOR'], вы получите 127.0.0.1 (локальный хост). Логи, аналитика, геолокация — всё поломается. Хуже того, атакующий может подставить IP другого пользователя, чтобы выдать себя за него.
Решение: доверять заголовку X-Forwarded-For только в том случае, если REMOTE_ADDR равен IP вашего доверенного прокси. Никак иначе.
Лучшая практика 2025: универсальная функция получения IP
Я собрал проверенную логику, которая учитывает все современные реалии. Функция:
- Проверяет, идёт ли запрос через доверенные прокси (вы задаёте их список).
- Если да — извлекает первый IP из
X-Forwarded-For. - Если нет — возвращает
REMOTE_ADDR. - Фильтрует приватные и зарезервированные адреса (опционально).
<?php
/**
* Получает реальный IP адрес клиента.
*
* @param array $trustedProxies Список доверенных IP прокси
* @return string IP адрес или '0.0.0.0' если не удалось определить
*/
function getClientIP(array $trustedProxies = []): string {
$remoteAddr = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
// Если прокси доверенный — берём первый IP из X-Forwarded-For
if (in_array($remoteAddr, $trustedProxies, true)) {
$forwardedFor = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
$ips = explode(',', $forwardedFor);
$clientIP = trim($ips[0] ?? '');
if (filter_var($clientIP, FILTER_VALIDATE_IP)) {
return $clientIP;
}
}
// Иначе возвращаем REMOTE_ADDR
return $remoteAddr;
}
// Пример использования:
$trusted = ['173.245.48.0', '103.21.244.0']; // IP Cloudflare (пример)
$userIP = getClientIP($trusted);
echo "Ваш IP: " . htmlspecialchars($userIP);
?>
Как это работает: вы передаёте массив IP-адресов ваших прокси (Cloudflare публикует свои диапазоны). Если запрос пришёл оттуда — берём X-Forwarded-For. Если нет — используем REMOTE_ADDR. Подделка становится бесполезной, потому что злоумышленник не сможет подделать REMOTE_ADDR.
Специфические случаи: Cloudflare, nginx, балансировщики
Cloudflare
Cloudflare передаёт реальный IP в заголовке HTTP_CF_CONNECTING_IP. Проще всего проверять именно его, но не забывайте фильтровать REMOTE_ADDR:
<?php
function getCloudflareIP(): string {
$remote = $_SERVER['REMOTE_ADDR'] ?? '';
// Проверяем, что запрос от Cloudflare (по IP или по наличию заголовка)
if (!empty($_SERVER['HTTP_CF_CONNECTING_IP'])) {
// Дополнительно: можно проверить REMOTE_ADDR по списку Cloudflare
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}
return $remote;
}
?>
Nginx как reverse proxy
Если вы настроили nginx с proxy_set_header X-Real-IP $remote_addr;, то в PHP появится $_SERVER['HTTP_X_REAL_IP']. Это надёжнее, чем X-Forwarded-For, так как nginx сам подставляет IP клиента.
Балансировщики AWS, Google Cloud
Обычно используют стандартный X-Forwarded-For. Добавьте их IP в список доверенных прокси, и функция выше отработает корре