nginx: заголовки безопасности, скрытие версии и настройки TLS

Веб-сервер с настройками по умолчанию работает, но сообщает посетителям и сканерам лишнее: точную версию nginx, отсутствие защитных заголовков, поддержку старых протоколов. Несколько директив снижают риск и убирают шум в отчётах сканеров. Ниже проверенная конфигурация: общий файл настроек, файл заголовков безопасности, пример сервера и проверка результата. Особое внимание: правило наследования add_header, из-за которого заголовки молча пропадают.
Статья опирается на логику направления «Сетевая безопасность» из методики зрелости Группы Сбер (источник); это методика для крупных компаний, мы используем её как ориентир и пересказываем своими словами. Конфигурация проверена на nginx 1.31 в одноразовом контейнере. Версию nginx и параметры сборки проверьте у себя: директива http2 on; поддерживается с версии 1.25.1, в более старых используйте listen 443 ssl http2;. Перед применением на рабочем сервере выполните nginx -t и держите откат: управление изменениями.
Что должно получиться
| Уровень | Что это значит на практике |
|---|---|
| 1. Базовый | На границе стоит межсетевой экран, наружу открыты только нужные порты |
| 2. Гигиенический | Сеть сегментирована, правила пересматриваются; публичные сервисы настроены безопасно и защищены от типовых атак |
Критерии сформулированы нами по мотивам методики, это ориентир, а не оценка соответствия. Дополнительно: настройка SSL в nginx и WAF и ограничения запросов.
Файл 1. Общие настройки
Сохраните как /etc/nginx/conf.d/security.conf. Он подключается в контекст http. Директива keepalive_timeout здесь намеренно не повторяется: она уже задаётся в основном nginx.conf, и повторное объявление приводит к ошибке при проверке конфигурации.
# /etc/nginx/conf.d/security.conf: общие настройки безопасности (контекст http)
server_tokens off; # не показывать версию nginx в заголовках и страницах ошибок
client_max_body_size 10m; # ограничение размера тела запроса
client_body_timeout 15s;
client_header_timeout 15s;
send_timeout 15s;
ssl_protocols TLSv1.2 TLSv1.3; # старые версии протокола отключены
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
server_tokens offубирает версию nginx из заголовкаServerи страниц ошибок. Версия остаётся в заголовке как просто «nginx»; скрытие версии — не защита, а снижение информативности для сканеров.- Ограничения размера тела и таймауты сокращают поверхность для медленных атак; значения подберите под приложение (загрузка файлов требует большего размера).
- TLS 1.2 и 1.3 разрешены, старые протоколы отключены; перечень шифров оставлен по умолчанию сервера (для современных версий он безопасен): не копируйте длинные списки из старых руководств.
ssl_session_tickets offснижает риск повторного использования ключей сеансов.
Файл 2. Заголовки безопасности
Сохраните как /etc/nginx/snippets/headers.conf. Это отдельный файл, который вы подключаете в каждый server и location, где есть свои add_header.
# /etc/nginx/snippets/headers.conf: заголовки безопасности; подключайте в каждом server и location,
# где есть собственные add_header (иначе унаследованные заголовки пропадают)
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
| Заголовок | Что делает |
|---|---|
| X-Content-Type-Options: nosniff | Запрещает браузеру угадывать тип содержимого |
| X-Frame-Options: SAMEORIGIN | Запрещает встраивать страницы в чужие фреймы (защита от кликджекинга) |
| Referrer-Policy | Ограничивает, сколько адреса уходит в заголовке Referer на другие сайты |
| Permissions-Policy | Отключает ненужные возможности браузера (геолокация, камера, микрофон) |
Если на сайте нужны встроенные карты, видео или формы с чужих доменов, заголовки придётся ослабить: проверяйте работу страниц после включения. Заголовок Content-Security-Policy мощнее, но требует тщательной настройки под ваши скрипты, поэтому включайте его отдельно, сначала в режиме отчёта (Content-Security-Policy-Report-Only).
Ловушка add_header: почему заголовки пропадают
В nginx директивы add_header наследуются с внешнего уровня на внутренний только если на внутреннем уровне нет собственных add_header. Если вы задали заголовки в http, а в server добавили, например, HSTS, то заголовки из http перестанут отправляться. Во время проверки выяснилось именно так: при заголовках безопасности в общем файле и HSTS в блоке сервера в ответе остались только HSTS и два заголовка, объявленных рядом, а остальные исчезли без ошибок и предупреждений. Решение, которое сработало: хранить заголовки в отдельном файле и подключать его в каждом месте, где есть свои add_header (директива include). Параметр always добавляет заголовки и к ответам с ошибками (404, 403, 500).
Файл 3. Пример сервера
Пример /etc/nginx/conf.d/site.conf: HTTPS-сервер, перенаправление с HTTP, HSTS, закрытые служебные файлы. Замените имя и пути сертификатов.
# /etc/nginx/conf.d/site.conf: пример сервера (замените пути сертификатов и имя)
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/nginx/certs/example.com.crt;
ssl_certificate_key /etc/nginx/certs/example.com.key;
# HSTS включайте после проверки, что весь сайт работает по HTTPS (меняется на год)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# остальные заголовки подключаем файлом: add_header из внешнего уровня не наследуется, если на уровне ниже есть свой
include /etc/nginx/snippets/headers.conf;
location / {
root /usr/share/nginx/html;
index index.html;
}
# закрыть служебные файлы и каталоги
location ~ /\.(?!well-known) {
deny all;
}
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
- HSTS сообщает браузерам, что сайт всегда нужно открывать по HTTPS. Включайте после проверки, что весь сайт и поддомены работают по HTTPS: откатить его на стороне клиентов нельзя до истечения срока (здесь год). Для начала используйте небольшое значение (например,
max-age=300). - Закрытие скрытых файлов. Правило запрещает доступ к путям, начинающимся с точки (
.env,.git), кроме.well-known(нужен для выпуска сертификатов). - Перенаправление. Код 301 с HTTP на HTTPS сохраняет запрошенный адрес.
Применение
sudo mkdir -p /etc/nginx/snippets
sudo nginx -t
sudo systemctl reload nginx
Что должно получиться: nginx -t выводит «syntax is ok» и «test is successful». При ошибке перезапуск не выполняйте: исправьте строку, указанную в сообщении. Перезагрузка (reload) применяет конфигурацию без разрыва соединений.
Проверка результата
curl -sI https://example.com/
curl -sI https://example.com/nope
curl -sI http://example.com/
curl -sI --tlsv1.1 --tls-max 1.1 https://example.com/
Ожидаемый результат на тестовой конфигурации:
| Проверка | Что должно быть |
|---|---|
| Ответ 200 | Протокол HTTP/2; заголовок server: nginx без версии; в ответе strict-transport-security, x-content-type-options, x-frame-options, referrer-policy, permissions-policy |
| Ответ 404 (несуществующая страница) | Те же заголовки безопасности (благодаря always) |
| Запрос по HTTP | Ответ 301 и Location: https://example.com/... |
| Запрос по TLS 1.1 | Соединение отклонено (curl не получает ответ); TLS 1.2 и 1.3 принимаются |
Запрос /.env |
Ответ 403 |
Более подробная проверка сертификатов и протоколов: проверка TLS своего сайта.
Когда за nginx стоит приложение или прокси
- Приложение (PHP, Python, Java) может само добавлять заголовки и раскрывать версию: отключите
expose_phpв PHP, уберите заголовокX-Powered-Byна стороне приложения или прокси (proxy_hide_header X-Powered-By;). - Если перед nginx стоит другой прокси (Traefik, балансировщик), то заголовки можно задавать там, но не дублируйте их в двух местах с разными значениями.
- При проксировании не забывайте о передаче настоящего адреса клиента для журналов и ограничений: настройка модуля realip.
Диагностика
| Симптом | Причина и что делать |
|---|---|
nginx -t: директива duplicate |
Директива уже объявлена в основном конфиге. Удалите повтор или изменяйте существующее значение |
| Часть заголовков не приходит | Наследование add_header: подключите файл заголовков во все блоки, где есть собственные add_header |
| Страницы перестали загружать внешние виджеты | Заголовки слишком строгие. Ослабьте политику конкретных заголовков и проверьте страницу |
| Браузер продолжает открывать только HTTPS после отката HSTS | HSTS уже закэширован в браузерах на срок max-age. Поэтому начинайте с малого значения |
| Версия nginx по-прежнему видна | Заголовок добавляет другой слой (прокси, контейнер) или модуль. Проверьте ответ на каждом уровне |
Где это в модели зрелости
Материал относится к направлению «Сетевая безопасность» (материалы направления, карта зрелости). Уровень 2 требует управляемого периметра и защиты публичных сервисов: безопасная конфигурация веб-сервера — базовая часть такой защиты.
Как проверить
nginx -tпроходит, перезагрузка выполнена без ошибок.- Команды
curl -sIпоказывают заголовки безопасности как в 200, так и в 404-ответах, версия nginx скрыта. - HTTP перенаправляется на HTTPS, TLS 1.1 отклоняется, TLS 1.2 и 1.3 работают.
- Служебные файлы (
.env,.git) недоступны, сайт работает без ошибок в браузере.
Источники
- Методика оценки уровня зрелости кибербезопасности компании, Группа Сбер: sberbank.ru.
- Документация nginx: модуль ngx_http_headers_module (
add_headerи наследование): nginx.org; модуль ngx_http_ssl_module: nginx.org. - OWASP Secure Headers Project: owasp.org. Международная практика, не российский норматив.






















