Клиентский IP в PHP: как не ошибиться в 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: почему «просто 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

Я собрал проверенную логику, которая учитывает все современные реалии. Функция:

  1. Проверяет, идёт ли запрос через доверенные прокси (вы задаёте их список).
  2. Если да — извлекает первый IP из X-Forwarded-For.
  3. Если нет — возвращает REMOTE_ADDR.
  4. Фильтрует приватные и зарезервированные адреса (опционально).
<?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 в список доверенных прокси, и функция выше отработает корре

Блоги

Рекомендаций по книгам