HTTP_HOST vs SERVER_NAME в PHP: в чём подвох?

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

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

Начать курс

PHP-детектив: что скрывается за HTTP_HOST и SERVER_NAME?

Если вы когда-нибудь заглядывали в суперглобальный массив $_SERVER в PHP, то наверняка видели там два очень похожих ключа: HTTP_HOST и SERVER_NAME. На первый взгляд кажется, что это одно и то же — имя сервера. Но опытные разработчики знают: дьявол кроется в деталях. Эта тема собрала тысячи просмотров на Stack Overflow, и неспроста. Неправильный выбор может привести к багам в редиректах, сбоям в кросс-доменных запросах и даже к уязвимостям.

В этой статье мы не просто перечислим сухие определения. Мы проведём настоящее расследование: разберём, откуда берутся эти значения, как они ведут себя на разных серверах (Apache, Nginx), и, самое главное, — когда какой вариант использовать. Пристегнитесь, будет интересно.

Что такое HTTP_HOST?

HTTP_HOST — это значение заголовка Host, который браузер (или любой другой HTTP-клиент) отправляет на сервер в составе запроса. Этот заголовок обязателен в HTTP/1.1 и содержит доменное имя и, возможно, номер порта.

Пример запроса от браузера:

GET /page.php HTTP/1.1
Host: pythonlib.ru:8080
User-Agent: Mozilla/5.0
...

В PHP это значение попадает в $_SERVER['HTTP_HOST']. Обратите внимание: это не проверенное значение. Клиент может отправить любой текст в заголовке Host, например, evil-site.com или 192.168.1.1. Именно здесь кроется главная опасность.

Что такое SERVER_NAME?

SERVER_NAME — это значение, которое устанавливает сам веб-сервер на основе своей конфигурации. Обычно оно берётся из директивы ServerName в Apache или server_name в Nginx. Это имя виртуального хоста, который обработал запрос.

Пример конфигурации Apache:

<VirtualHost *:80>
    ServerName pythonlib.ru
    DocumentRoot /var/www/pythonlib
</VirtualHost>

В этом случае $_SERVER['SERVER_NAME'] будет равен pythonlib.ru. В отличие от HTTP_HOST, это значение контролируется администратором сервера, а не клиентом. Однако есть нюанс: SERVER_NAME может не содержать порт (зависит от настройки UseCanonicalName).

Ключевые различия в таблице

Для наглядности сведём всё в таблицу:

Параметр HTTP_HOST SERVER_NAME
Источник Заголовок HTTP-запроса от клиента Конфигурация веб-сервера
Контроль Клиент (может быть подделан) Администратор сервера
Порт Включает порт, если он нестандартный (например, site.com:8080) Обычно без порта (зависит от UseCanonicalName)
Надёжность Низкая (не доверяйте без проверки) Высокая (но не абсолютная)
Использование Редиректы, ссылки для пользователя Внутренняя логика, безопасность

Когда что использовать? Практические примеры

Теперь перейдём к самому важному — коду. Рассмотрим три типичных сценария.

1. Редирект на тот же домен (например, с www на без www)

Здесь логично использовать HTTP_HOST, потому что мы хотим перенаправить пользователя именно туда, откуда он пришёл, но с изменённым префиксом.

<?php
// Редирект с www на без www
if (strpos($_SERVER['HTTP_HOST'], 'www.') === 0) {
    $new_host = substr($_SERVER['HTTP_HOST'], 4);
    header('Location: https://' . $new_host . $_SERVER['REQUEST_URI']);
    exit;
}
?>

Почему не SERVER_NAME? Потому что если сервер настроен на example.com, а пользователь зашёл по www.example.com, то SERVER_NAME вернёт example.com, и редирект не сработает корректно.

2. Генерация абсолютных ссылок в письмах (безопасность)

Если вы отправляете email со ссылкой для сброса пароля, никогда не используйте HTTP_HOST. Представьте, что злоумышленник отправляет запрос с поддельным заголовком Host: evil.com. Ваш скрипт сгенерирует ссылку на evil.com, и пользователь перейдёт на фишинговый сайт.

<?php
// НЕПРАВИЛЬНО: опасно!
$link = 'https://' . $_SERVER['HTTP_HOST'] . '/reset-password?token=' . $token;

// ПРАВИЛЬНО: используем SERVER_NAME
$link = 'https://' . $_SERVER['SERVER_NAME'] . '/reset-password?token=' . $token;
?>

3. Определение текущего домена для API

В REST API часто нужно вернуть клиенту ссылку на самого себя. Здесь компромисс: можно использовать HTTP_HOST, но с обязательной валидацией.

<?php
function getSafeHost() {
    $allowed_hosts = ['pythonlib.ru', 'www.pythonlib.ru', 'api.pythonlib.ru'];
    $host = parse_url($_SERVER['HTTP_HOST'], PHP_URL_HOST); // отрезаем порт
    if (in_array($host, $allowed_hosts, true)) {
        return $host;
    }
    // Если хост не в белом списке — падаем или используем SERVER_NAME
    return $_SERVER['SERVER_NAME'];
}

$host = getSafeHost();
$self_link = 'https://' . $host . '/api/v1/users';
?>

Подводные камни и особенности серверов

Теория теорией, но на практике вас могут ждать сюрпризы.

  • Apache + UseCanonicalName: Если в конфигурации Apache установлена директива UseCanonicalName On, то SERVER_NAME будет равен значению из ServerName. Если Off (по умолчанию) — SERVER

Блоги

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