IP-адрес клиента в PHP: как не ошибиться в 2025 году

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

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

Начать курс

Охота за IP: почему просто $_SERVER['REMOTE_ADDR'] уже не торт?

Представьте: вы запустили крутой образовательный проект на PHP. Всё летает, пользователи довольны. Но тут вы решаете добавить логирование посетителей, географическую привязку или, скажем, защиту от накрутки голосов. И тут — бац! — выясняется, что половина ваших "уникальных" посетителей сидит с одним и тем же IP: 127.0.0.1 или 10.0.0.1. Знакомо?

Проблема получения реального IP-адреса клиента в PHP — это классика, которая не теряет актуальности. Вопрос с Stack Overflow набирает тысячи просмотров, а новички (и не только) продолжают наступать на одни и те же грабли. В 2025 году, когда Cloudflare, AWS, Nginx и куча CDN-прокси — это стандарт де-факто, полагаться на $_SERVER['REMOTE_ADDR'] — это как гадать на кофейной гуще.

В этой статье мы не просто дадим готовую функцию. Мы разберём анатомию HTTP-заголовков, научимся отличать друга от врага и напишем безопасный и надёжный способ получения IP-адреса посетителя в PHP, который не подведёт вас в 2025 году. Поехали!

REMOTE_ADDR: ваш верный, но слепой пёс

Начнём с базы. $_SERVER['REMOTE_ADDR'] — это IP-адрес того, кто напрямую подключился к вашему веб-серверу. Если ваш сайт висит на выделенном сервере без прокси — это и есть IP клиента. Просто и надёжно.

Но в современном мире так бывает редко. Чаще всего между пользователем и вашим PHP-скриптом стоит целая цепочка:

  • CDN (Cloudflare, Fastly)
  • Балансировщик нагрузки (Nginx, HAProxy)
  • Обратный прокси (Varnish, Nginx)
  • Хостинг с общей архитектурой

И вот тут REMOTE_ADDR показывает IP последнего звена (например, вашего балансировщика), а не реального пользователя. Для образовательного сайта это катастрофа: вы не сможете отличить студента из Москвы от студента из Нью-Йорка, а система антиспама будет блокировать всех подряд.

HTTP-заголовки: где прячется настоящий IP?

Чтобы передать реальный IP клиента, прокси-серверы добавляют специальные HTTP-заголовки. Самые популярные в 2025 году:

1. X-Forwarded-For (XFF)

Классический заголовок. Может содержать цепочку IP: client_ip, proxy1_ip, proxy2_ip. Первый IP в списке — это настоящий клиент. Но есть нюанс: этот заголовок может подделать любой злоумышленник, если ваша инфраструктура его не чистит.

2. X-Real-IP

Любимый заголовок Nginx. Содержит только один IP — реальный. Проще и безопаснее, но поддерживается не всеми прокси.

3. CF-Connecting-IP (Cloudflare)

Специфический заголовок Cloudflare. Если вы используете их CDN, это самый надёжный вариант. Но только если вы доверяете Cloudflare (а вы должны, они молодцы).

Важно: Никогда не доверяйте заголовкам X-Forwarded-For и X-Real-IP от неизвестных источников. Злоумышленник может подставить любой IP, и ваша система будет считать его реальным.

Лучшая практика 2025: многослойная защита

Как же быть? Мы не можем просто брать первый IP из X-Forwarded-For — это опасно. Но и игнорировать прокси нельзя. Решение — белый список доверенных прокси.

Идея проста: мы знаем IP-адреса наших балансировщиков и CDN. Если запрос пришёл от них — мы доверяем заголовкам. Если нет — используем REMOTE_ADDR.

Вот рабочий класс на PHP, который реализует эту логику:

<?php

class ClientIP
{
    /**
     * Список доверенных IP-адресов и подсетей (CIDR).
     * Добавьте сюда IP ваших прокси, CDN, балансировщиков.
     */
    private static array $trustedProxies = [
        '127.0.0.1',          // localhost
        '10.0.0.0/8',         // приватные сети
        '172.16.0.0/12',      // приватные сети
        '192.168.0.0/16',     // приватные сети
        '173.245.48.0/20',    // Cloudflare (пример)
        '103.21.244.0/22',    // Cloudflare (пример)
        // Добавьте свои IP
    ];

    /**
     * Получить реальный IP клиента.
     *
     * @return string IP-адрес или 'UNKNOWN', если определить не удалось.
     */
    public static function get(): string
    {
        $remoteAddr = $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN';

        // Проверяем, пришёл ли запрос от доверенного прокси
        if (!self::isTrustedProxy($remoteAddr)) {
            return $remoteAddr;
        }

        // Если прокси доверенный, проверяем заголовки
        $headers = [
            'HTTP_CF_CONNECTING_IP', // Cloudflare
            'HTTP_X_REAL_IP',        // Nginx
            'HTTP_X_FORWARDED_FOR',  // Стандартный X-Forwarded-For
        ];

        foreach ($headers as $header) {
            if (!empty($_SERVER[$header])) {
                $ip = self::extractClientIp($header, $_SERVER[$header]);
                if ($ip && filter_var($ip, FILTER_VALIDATE_IP)) {
                    return $ip;
                }
            }
        }

        return $remoteAddr;
    }

    /**
     * Проверить, является ли IP доверенным прокси.
     */
    private static function isTrustedProxy(string $ip): bool
    {
        foreach (self::$trustedProxies as $proxy) {
            if (strpos($proxy, '/') !== false) {
                // CIDR проверка
                if (self::ipInCidr($ip, $proxy)) {
                    return true;
                }
            } else {
                // Точное совпадение
                if ($ip === $proxy) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Извлечь IP из заголовка X-Forwarded-For (цепочка).
     */
    private static function extractClientIp(string $headerName, string $headerValue): ?string
    {
        // Для CF-Connecting-IP и X-Real-IP — это всегда один IP
        if ($headerName !== 'HTTP_X_FORWARDED_FOR') {
            return trim($headerValue);
        }

        // Для X-Forwarded-For берём первый IP из списка
        $ips = explode(',', $headerValue);
        $firstIp = trim($ips[0]);

        return $firstIp ?: null;
    }

    /**
     * Проверить, входит ли IP в CIDR-диапазон.
     */
    private                

Блоги

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