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