HSTS: что это и как настроить заголовок
Что такое HSTS и зачем он нужен
HSTS (HTTP Strict Transport Security) — механизм, при котором сервер сообщает браузеру: этот сайт нужно открывать только по HTTPS, и так будет всегда в течение указанного срока. Технически всё сводится к одному HTTP-заголовку ответа — Strict-Transport-Security. Отдельных файлов, API или настроек на стороне клиента не нужно: достаточно, чтобы сервер отдавал заголовок по HTTPS.
Проблема, которую решает HSTS, называется SSL-stripping. Пользователь вводит example.com, браузер сначала пробует открыть http://example.com, а злоумышленник в той же сети (открытый Wi-Fi, скомпрометированный роутер, вредоносный прокси) перехватывает этот запрос и не даёт серверу ответить редиректом на HTTPS. Вместо защищённой версии сайта пользователь получает копию на HTTP — с подменённой страницей логина. HTTPS при этом не «взломан»: сам первый переход был незашифрованным, и именно эту лазейку закрывает HSTS.
Когда браузер получил HSTS-заголовок, он запоминает домен и дальше:
- переписывает любые
http://наhttps://ещё до отправки запроса — открытый канал не используется вообще; - блокирует клик «всё равно перейти» на предупреждении о невалидном сертификате (у обычного HTTPS такое обойти можно);
- не даёт принять самоподписанный или чужой сертификат для этого домена.
Обратите внимание на важный нюанс: браузер запоминает политику не на сервере, а у себя. Это значит, что HSTS не работает при первом посещении сайта (браузер ещё ничего не знает) и что «отозвать» политику мгновенно нельзя — об этом ниже, в разделе про preload.
Как выглядит заголовок Strict-Transport-Security
Заголовок компактный и состоит из самого имени, параметра max-age и опциональных директив:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Разберём по частям:
Strict-Transport-Security— имя заголовка, чувствительное к регистру только формально; значение разбирается браузером строго.max-age=31536000— сколько секунд браузер обязан помнить политику. 31 536 000 секунд — это 365 дней. Параметр обязателен: без него заголовок игнорируется полностью.includeSubDomains— распространить политику на все поддомены текущего домена.preload— заявка на включение домена в предустановленный список браузеров.
Заголовок имеет смысл только в ответе по HTTPS. Если сервер отдаёт Strict-Transport-Security в ответе на http://, браузер его проигнорирует — это защита от подмены заголовка посредником в открытом канале.
Типичный ответ сервера с включённым HSTS выглядит так:
curl -I https://example.com
HTTP/2 200
server: nginx
strict-transport-security: max-age=31536000; includeSubDomains; preload
content-type: text/html; charset=utf-8
Ключевая строка здесь — третья. Если её нет, HSTS на сайте не настроен, даже если HTTPS работает и редирект с HTTP на HTTPS стоит корректно. Это две разные вещи: редирект — серверная логика, HSTS — инструкция браузеру.

Параметр max-age: сколько живёт политика
max-age — самая ответственная часть настройки. Он определяет, как долго браузер будет принудительно использовать HTTPS, даже если заголовок исчезнет с сервера. Логика простая и жёсткая: пока счётчик не истёк, браузер про http:// для этого домена не вспомнит.
Разумный порядок внедрения выглядит так:
| Этап | Значение max-age | Что проверяем |
|---|---|---|
| Тестовая раскатка | 300 (5 минут) |
HTTPS работает на всех страницах, нет смешанного контента |
| Первая неделя | 86400 (1 сутки) |
Нет битых ресурсов, редиректы не зациклились |
| Уверенная работа | 2592000 (30 суток) |
Поддомены и мобильные приложения не ломаются |
| Боевое значение | 31536000 (1 год) |
Готовы к долгосрочной политике |
Начинать сразу с года — распространённая ошибка. Если на сайте остался хоть один ресурс, доступный только по HTTP (картинка на CDN, скрипт старой аналитики, виджет), браузеры начнут блокировать его, а вы не сможете быстро откатиться: пользователи уже «запомнили» длинную политику. Короткий max-age на старте даёт вам право на ошибку с откатом в течение часа.
Увеличивать значение нужно только после того, как убедились: смешанного контента нет, все поддомены под HTTPS, внутренние ссылки не ведут на HTTP-адреса вручную. Проверить смешанный контент удобно в консоли браузера — предупреждения вида Mixed Content: The page at ... was loaded over HTTPS, but requested an insecure resource указывают точный URL проблемного ресурса.
includeSubDomains: когда включать и когда подождать
Директива includeSubDomains распространяет HSTS на все поддомены: www, api, static, mail, staging и любые другие. Это удобно, потому что закрывает всю зону сразу, но требует подготовки.
Перед включением проверьте каждый поддомен, который реально используется:
- Все ли поддомены в DNS и отдают HTTPS? Если
dev.example.comуказывает на старый сервер без сертификата, пользователи этого поддомена получат ошибку сертификата, а обойти её нажатием кнопки не смогут — HSTS блокирует такое «продолжить всё равно». - Есть ли поддомены под маркетинговые и партнёрские сервисы? Внешние площадки, которые вы не контролируете, тоже попадают под политику.
- Не отдаёт ли какой-то поддомен только HTTP? Например, внутренний репозиторий или устаревшая панель. Их нужно либо перевести на HTTPS, либо исключить из зоны политики.
Если хоть один поддомен не готов, от includeSubDomains на текущем этапе лучше отказаться и включать его позже, вместе с повышением max-age. Добавить директиву можно в любой момент — это безопаснее, чем убирать её потом, потому что удаление тоже не мгновенно.
Отдельно стоит упомянуть apex-домен: HSTS не покрывает домен автоматически из www-версии и наоборот. Заголовок на www.example.com не защищает example.com и наоборот, если только includeSubDomains не указан на apex-домене. Практика: ставить заголовок на оба хоста, отвечающих реальному трафику.
preload: что это и почему это почти необратимо
Директива preload — это заявка на добавление домена в список, который браузеры поставляют «в коробке». Такой домен принудительно открывается по HTTPS ещё до первого ответа сервера и даже без сохранённой локальной политики. Это закрывает упомянутую выше дыру «первого посещения».
Но у preload есть свойство, о котором нужно знать заранее: попадание в список практически необратимо в моменте. Удаление из списка — не мгновенный процесс, а отдельная процедура, и обновления браузеров доходят до пользователей постепенно, месяцами. Пока обновление не доставлено, браузер продолжит заходить только по HTTPS.
Из этого следуют практические правила:
- Не ставьте
preloadэкспериментом. Это осознанное решение для стабильного домена, который гарантированно живёт по HTTPS на всех поддоменах. - Требования входа строгие. Формально нужен
max-ageне меньше 31536000 и директиваincludeSubDomains, а также перенаправление HTTP → HTTPS на самом корневом домене. - Перед подачей заявки протестируйте все поддомены. Ошибка на
beta.илиcdn.поддомене после попадания в список означает недоступность сервиса для пользователей, которых вы не сможете вернуть на HTTP. - Учитывайте время. Даже успешное удаление из списка не отменяет
max-age, который браузер уже сохранил локально.
Проще говоря: preload — это финальный шаг зрелой конфигурации, а не флаг «сделать безопаснее». Включайте его, когда HTTPS-инфраструктура перестала меняться.


Как настроить HSTS: nginx, Apache, статические хостинги
Настройка сводится к добавлению одной строки в конфигурацию сервера. Ниже — рабочие примеры для распространённых стеков.
nginx — добавляем заголовок в блок server, обслуживающий 443:
server {
listen 443 ssl http2;
server_name example.com www.example.com;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# остальные директивы
}
Важная деталь: директива add_header с параметром always применяется и к ответам с кодом 4xx/5xx, где по умолчанию add_header не срабатывает. Если не поставить always, страницы ошибок будут отдаваться без HSTS — а именно через страницы ошибок любят работать некоторые атаки.
Apache (модуль mod_headers должен быть включён):
<VirtualHost *:443>
ServerName example.com
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</VirtualHost>
Статический хостинг или панель управления — заголовок обычно задаётся в разделе «Security Headers» или через файл правил (например, _headers / netlify.toml-подобного формата):
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains
После настройки обязательно проверьте, что заголовок отдаётся на всех ответах, а не только на главной. Частая ситуация: add_header прописан в локации /, но не в /api или /static. Тогда политика не применяется к запросам, где она тоже нужна.
Отдельно стоит проверить, не конфликтует ли HSTS с CDN или реверс-прокси перед сервером. Если заголовок ставит промежуточный слой, а ваш веб-сервер — свой, браузер увидит тот, что дошёл последним. Убедитесь, что значения не противоречат друг другу: два разных max-age в одном ответе приведут к непредсказуемому поведению.
Проверка: curl, openssl, браузер
Первый шаг диагностики — посмотреть, что реально отдаёт сервер:
curl -sI https://example.com | grep -i strict-transport-security
Разбираем возможные результаты:
strict-transport-security: max-age=31536000; includeSubDomains— заголовок есть, параметры корректны.- Пустой вывод — заголовок не отдаётся вообще. Смотрите конфигурацию веб-сервера.
max-age=0— политика отключена. Так иногда «гасят» HSTS перед откатом; для рабочего сайта это означает отсутствие защиты.- Заголовок есть в ответе на
http://— он будет проигнорирован браузером. Проверяйте только HTTPS-ответы.
Проверить, что заголовок не отдаётся в открытом канале (что правильно):
curl -sI http://example.com | grep -i strict-transport-security
Вывод должен быть пустым — здесь мы ожидаем только редирект (301/308) на HTTPS.
Проверить состояние сертификатов и что цепочка валидна на том же хосте поможет проверка SSL-сертификата — HSTS имеет смысл только поверх рабочего HTTPS. А убедиться, что редирект с HTTP на HTTPS не зациклился и ведёт куда надо, можно через проверку редиректов: зацикливание при включённом HSTS приводит к тому, что браузер вообще не может открыть сайт, и откатить это на стороне пользователя нельзя.
Быстрая сводка проверок браузера: в Chrome/Chromium адрес chrome://net-internals/#hsts позволяет посмотреть, есть ли домен в локальной HSTS-базе, и вручную удалить запись для тестов. Это удобно при отладке на dev-домене, где вы не хотите ждать истечения max-age.
Частые ошибки: таблица симптом → причина → решение
| Симптом | Причина | Решение |
|---|---|---|
| Заголовок не виден в ответе | add_header без always, либо прописан только в одном location |
Добавить always и продублировать в остальных блоках |
Браузер не переписывает http:// на https:// |
max-age отсутствует или равен 0 |
Указать max-age минимум 300 и перезайти на сайт |
Сайт ломается после включения includeSubDomains |
Поддомен не отдаёт HTTPS или имеет невалидный сертификат | Отключить директиву, привести поддомен в порядок, включить снова |
preload подан, но в браузере нет эффекта |
max-age меньше года или нет includeSubDomains |
Привести параметры к требованиям и подать заявку повторно |
| На странице ошибки нет HSTS | Директива без always |
Добавить always в add_header |
| После отката настроек сайт всё равно только по HTTPS | max-age ещё не истёк у пользователей |
Дождаться истечения; для новых визитов поставить max-age=0 |
Проверить комплексную картину безопасности помогает проверка security-заголовков: она показывает, какие защитные заголовки отдаёт сервер и нет ли среди них противоречивых значений. HSTS стоит настраивать в связке с другими заголовками — в первую очередь с теми, что ограничивают выполнение скриптов. Логичное продолжение темы — статья про Content-Security-Policy, которая закрывает уже не транспорт, а сам контент страницы.
Короткий итог
HSTS — это один заголовок, который превращает HTTPS из «рекомендации сервера» в «обязательное правило браузера». Настраивается он за несколько минут, но последствия у неверных параметров долгие: браузер запоминает политику на весь срок max-age, а preload добавляет к этому список, который обновляется у пользователей не сразу.
Практический порядок действий: убедитесь, что весь сайт и поддомены стабильно работают по HTTPS, поставьте заголовок с коротким max-age, проверьте все страницы на смешанный контент, постепенно увеличьте срок до года, и только потом рассматривайте includeSubDomains и preload.
Если хотите держать защитные заголовки под контролем и не отслеживать их вручную после каждого релиза, посмотрите на проверку доступности сайта и профильные инструменты UptimeChecker: они помогут регулярно подтверждать, что конфигурация безопасности на месте и отвечает так, как задумано.