Безопасность: заголовки, скрытие версии, ограничения

Рейтинг: 79.6% · 15 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Безопасность: заголовки, скрытие версии, ограничения

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения (вы здесь)
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Открой DevTools на любом своём проде, дёрни вкладку Network, посмотри на ответ. Видишь Server: nginx/1.30.0? Поздравляю, ты только что бесплатно подсказал атакующему, какой именно набор CVE на тебя натравить. А теперь спроси себя: куда уедет твой сайт во фрейм на чужом фишинговом домене, отдаёт ли он cookie через незашифрованный HTTP, и что произойдёт, если кто-то прямо сейчас откроет example.com/.git/config. Если на эти вопросы у тебя нет уверенного ответа - этот урок про твою боль.

Речь не про дорогой WAF и не про машинное обучение. Речь про baseline - тот минимум на уровне прокси, который закрывает 80 процентов глупых дыр десятком директив и не стоит ни рубля. nginx стоит на входе у всех твоих запросов, и именно тут дешевле всего навесить broneжилет: security-заголовки, скрытие версии, ограничение методов и тел, запрет служебных файлов. Дальше - механика каждого пункта, грабли и рабочий конфиг, который можно положить в globals/security.conf по образцу эталонного gateway.

nginx безопасность: модель угроз на прокси и почему заголовки решают

Сразу честно: nginx не чинит дыры в твоём бэкенде. Если PHP-код уязвим к SQL-инъекции, никакой security-заголовок его не спасёт. Заголовки работают в браузере жертвы - это инструкции, которые говорят браузеру "не делай вот так". Поэтому модель простая: ты на прокси добавляешь к каждому ответу набор HTTP-заголовков, браузер их читает и ужесточает свою политику. Атака, которая раньше проходила (загрузка твоей страницы во фрейм, выполнение инлайн-скрипта, утечка Referer на чужой домен), теперь блокируется самим браузером.

Ключевой технический нюанс в nginx - директива add_header капризная. Она наследуется в дочерний контекст (server -> location) ТОЛЬКО если в дочернем контексте нет ни одного своего add_header. Стоит тебе добавить хоть один add_header внутри location - все унаследованные из server исчезают. Это классическая грабля: навесил заголовки глобально, потом дописал в одном location add_header Cache-Control, и весь security-набор для этого location пропал. Поэтому в эталоне все заголовки идут с флагом always (чтобы слались и на ответах 4xx/5xx, а не только на 2xx/3xx), и важно держать их в одном месте либо аккуратно дублировать. Запомни: always добавляет заголовок к ответам с любым кодом, без него - только к успешным.

Изображение

nginx security headers: полный набор и что каждый делает

Разберём набор из эталонного global_security построчно - что, зачем и где грабли.

X-Content-Type-Options: nosniff - запрещает браузеру угадывать MIME-тип. Без него браузер может решить, что твой .txt с пользовательским контентом на самом деле HTML, и выполнить его как страницу. Дешёвая защита от целого класса XSS через загруженные файлы. Ставь всегда, побочек нет.

X-Frame-Options: SAMEORIGIN - защита от clickjacking. Запрещает встраивать твой сайт во фрейм на чужом домене. Технически устаревает в пользу CSP frame-ancestors, но старые браузеры понимают только X-Frame-Options, поэтому шлём оба. SAMEORIGIN разрешает фреймить только со своего origin.

Referrer-Policy: no-referrer - управляет тем, что уйдёт в заголовке Referer при переходе по ссылке. no-referrer - максимально жёстко, вообще ничего не отправляем. Если ломается аналитика - ослабь до strict-origin-when-cross-origin (отдаёт только origin при переходе на другой домен, полный URL - внутри своего).

Permissions-Policy: geolocation=(), microphone=(), camera=() - отключает мощные браузерные API для твоего сайта и встроенных в него фреймов. Пустые скобки = запрещено всем. Если сайту не нужна камера и микрофон - вырубай, чтобы внедрённый через XSS скрипт не смог их дёрнуть.

X-Download-Options: noopen и X-Permitted-Cross-Domain-Policies: none - легаси для старого IE и Flash/PDF-плагинов соответственно. Вреда от них ноль, в эталоне они есть для полноты, но в 2026 это уже почти археология. Знай что это, не трать на них нервы.

А теперь про две главные.

nginx hsts и Content-Security-Policy: два тяжеловеса

Strict-Transport-Security (HSTS) - заставляет браузер ходить к тебе ТОЛЬКО по HTTPS, даже если пользователь набрал http:// или кликнул по старой ссылке. После первого визита браузер запоминает политику на max-age секунд и сам апгрейдит все запросы до HTTPS, не давая шанса на downgrade-атаку (man-in-the-middle, который перехватывает первый незашифрованный запрос).

Код: Выделить всё

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
max-age 63072000 - это два года в секундах. includeSubDomains распространяет политику на все поддомены. preload - заявка на включение в зашитый в браузеры список (hstspreload.org), после которого браузер требует HTTPS даже при самом первом визите. И вот тут главная грабля: HSTS отдавай ТОЛЬКО на HTTPS-сервере (на listen 443), НИКОГДА на HTTP. И семь раз подумай перед includeSubDomains + preload - если у тебя есть хоть один поддомен без валидного сертификата, ты его себе отрубишь, а откатить preload из списка браузеров - это месяцы. В эталоне HSTS поэтому закомментирован в global_ssl и включается осознанно на конкретном https_server. Начинай с маленького max-age (например 300), убедись что всё работает, потом наращивай.

Content-Security-Policy (CSP) - это главный заголовок безопасности 2026 года и реальная замена устаревшему X-XSS-Protection. CSP - белый список источников, откуда браузеру разрешено грузить скрипты, стили, картинки, шрифты, фреймы. Если злоумышленник через XSS внедрил <script>укради-cookie</script>, но твоя CSP говорит "инлайн-скрипты запрещены, скрипты только с self" - браузер просто не выполнит инъекцию.

Код: Выделить всё

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;
Разбор: default-src 'self' - дефолт для всего, грузим только со своего origin. script-src 'self' - скрипты только свои, инлайн и eval запрещены (это и есть anti-XSS). object-src 'none' - убиваем <object>/<embed> (старые Flash-дыры). frame-ancestors 'self' - современная замена X-Frame-Options. base-uri 'self' - не дать инъекции переписать <base> и увести относительные URL. CSP - тонкая штука: слишком строгая ломает сайт (инлайн-скрипты аналитики, CDN со шрифтами), слишком слабая (unsafe-inline) бесполезна. Поэтому внедряют её через режим Content-Security-Policy-Report-Only: браузер не блокирует, а только шлёт отчёты о нарушениях, ты смотришь логи неделю, доводишь политику и только потом переключаешь на боевой заголовок.

ВАЖНО про X-XSS-Protection. Эталонный global_security его всё ещё шлёт - и это сознательное легаси. В 2026 OWASP прямо рекомендует ЛИБО не отправлять этот заголовок вовсе, ЛИБО слать X-XSS-Protection: 0. Старый XSS-аудитор браузеров был выпилен из Chrome и Edge ещё годы назад, а в ряде случаев сам создавал уязвимости (атакующий мог использовать фильтр, чтобы отключать легитимные куски страницы). Вывод на 2026: полагайся на CSP, а X-XSS-Protection либо выкинь, либо ставь в 0. Не копируй старые гайды, где он стоит в "1; mode=block" - это вредный совет из 2015 года.

nginx hide version: скрытие версии и заголовка Server

server_tokens off - первое, что делает любой адекватный admin. По умолчанию nginx светит полную версию в заголовке Server и на страницах ошибок. Атакующему это подарок: увидел nginx/1.30.0 - сразу знает, какие CVE применимы (а на 2026 актуальны CVE-2026-42945 в rewrite-модуле, CVE-2026-1642 и другие - см. урок по CVE).

Код: Выделить всё

http {
    server_tokens off;
}
Но есть нюанс: server_tokens off убирает только версию, а само слово "Server: nginx" остаётся. Полностью переписать или убрать заголовок Server ванильным nginx нельзя - нет такой директивы. Зато можно через модуль headers-more (ngx_http_headers_more_filter_module, в OpenResty он встроен, в эталоне как раз OpenResty):

Код: Выделить всё

# требует модуль headers_more (есть в OpenResty/Angie из коробки)
more_set_headers "Server: cyberlake-gw";
more_clear_headers "Server";
more_set_headers перепишет Server на что угодно, more_clear_headers - вырежет вовсе. В отличие от add_header, директивы more_* работают на любых ответах и не страдают от грабли с наследованием. Не переоценивай security-by-obscurity: скрытие версии не делает тебя неуязвимым, сканер всё равно нащупает фичи по поведению. Но это бесплатно отсекает массовый автоматический скан "дай мне всё на nginx/1.30.0" - и потому делается всегда.

Ограничения: методы, тело запроса и max_headers против флуда

max_headers - новая директива nginx 1.29.8 (вошла в stable 1.30). До неё ничто не мешало клиенту прислать тысячи мелких заголовков в одном запросе и сожрать память воркера - классический header-flooding DoS. Теперь есть лимит на число строк заголовков, по умолчанию 1000, превышение - сразу 400 Bad Request. Директива приехала из freenginx Дунаева. Если твой апстрим точно не ждёт сотни заголовков - ужесточи:

Код: Выделить всё

http {
    max_headers 100;
}
Ограничение HTTP-методов. Зачем твоему статическому сайту принимать PUT, DELETE, TRACE? Два подхода. Идиоматичный nginx-way - limit_except внутри location (разрешает перечисленные методы, остальные - 403):

Код: Выделить всё

location /api/ {
    limit_except GET POST {
        deny all;
    }
    proxy_pass http://backend_upstream;
}
Подход эталона (в app_redirects_security) - через if, возвращая корректный 405 Method Not Allowed:

Код: Выделить всё

if ($request_method !~ ^(GET|HEAD|POST)$) {
    return 405;
}
Разница: limit_except отдаёт 403 и работает только для не-перечисленных методов внутри location; if с return 405 семантически честнее (405 - это "метод не разрешён", 403 - "доступ запрещён") и кладётся выше по дереву. Помни вечное правило "if is evil" - if внутри location коварен с try_files и rewrite, но для голого return 405 на уровне server он безопасен.

client_max_body_size - сколько максимум байт примем в теле запроса. По умолчанию 1m. Если у тебя API, который не грузит файлы, ставь жёстко - так ты режешь попытки залить гигабайтный мусор и забить диск/память:

Код: Выделить всё

http {
    client_max_body_size 10m;   # глобально скромно
}
location /upload/ {
    client_max_body_size 100m;  # точечно поднять там где реально нужно
}
Превышение - 413 Request Entity Too Large. Заодно глянь client_body_buffer_size и client_header_timeout - но это уже тонкая настройка, baseline закрывается body_size и max_headers.

Запрет служебных файлов и query-фильтры: deny .git, .env и пределы фильтрации

Огромный пласт утечек - это банально открытые служебные файлы. Кто-то задеплоил через git pull и оставил .git/ в webroot - и вот уже example.com/.git/config, а через него вся история коммитов с паролями в старых версиях. Или .env с ключами от базы. Эталонный app_redirects_security это закрывает regex-ом:

Код: Выделить всё

# скрытые dotfiles, кроме .well-known (нужен для ACME и пр.)
location ~ /\.(?!well-known) {
    deny all;
    access_log off;
    log_not_found off;
}

# .git/.svn/.hg, .env, бэкапы и дампы
location ~* \.(git|svn|hg|env|bak|old|orig|sql|zip|tar|gz)$ {
    deny all;
}
location ~* /(\.git|\.env|composer\.(json|lock)|\.htaccess) {
    deny all;
}
Деталь, которую все ломают: исключение для .well-known. Если ты глухо запретишь все dotfiles через location ~ /\., у тебя отвалится ACME-проверка (Let's Encrypt кладёт токен в /.well-known/acme-challenge/) и перестанут выпускаться сертификаты. Поэтому негативный lookahead (?!well-known) обязателен. access_log off и log_not_found off на этих локациях - чтобы боты не засирали тебе логи тоннами 403.

Простые SQLi/XSS-фильтры по query_string. Эталон фильтрует подозрительные строки запроса - грубо режет очевидный мусор:

Код: Выделить всё

location / {
    if ($query_string ~* "union.*select|concat.*\(|GLOBALS|_REQUEST|<script") {
        return 403;
    }
    if ($query_string ~* "(\.\./|%2e%2e)") {   # path traversal
        return 403;
    }
    # ...
}
Это работает против тупых автосканеров и скрипт-кидди, и стоит почти ничего. Но честно про пределы: это НЕ WAF и не замена нормальной валидации на бэкенде. Такие regex-фильтры тривиально обходятся (кодирование, регистр, разбивка union/**/select, двойное URL-кодирование), а ещё дают ложные срабатывания на легитимных запросах (пользователь искал слово "select" в магазине - получил 403). Используй их как первый грубый щит и шумоподавитель, но настоящую защиту от инъекций делает параметризованный SQL на бэке, а серьёзную фильтрацию - выделенный WAF (modsecurity, coraza, облачный). nginx-фильтры - это бордюр, а не стена.

Мини-лаба: собрать security.conf и проверить заголовки руками

Повтори по шагам на тестовом nginx (1.30+ или OpenResty/Angie):
  • Создай файл globals/security.conf и положи в него: server_tokens off; max_headers 100; client_max_body_size 10m; и весь набор add_header с флагом always (X-Content-Type-Options nosniff, X-Frame-Options SAMEORIGIN, Referrer-Policy no-referrer, Permissions-Policy, CSP с default-src 'self').
  • Подключи include globals/security.conf; внутри http {} в основном конфиге.
  • Проверь синтаксис: nginx -t. Получил "syntax is ok, test is successful" - перезагружай: nginx -s reload.
  • Дёрни заголовки: curl -sI https://localhost/ и убедись, что в ответе есть твои заголовки, а Server не светит версию.
  • Проверь always: curl -sI https://localhost/несуществует - на 404 заголовки должны быть на месте (если флага always нет - они пропадут, в этом весь смысл).
  • Проверь deny: curl -s -o /dev/null -w "%{http_code}\n" https://localhost/.git/config - должен вернуть 403.
  • Проверь метод: curl -s -o /dev/null -w "%{http_code}\n" -X DELETE https://localhost/api/ - должен вернуть 405 (или 403 для limit_except).
  • Проверь max_headers: сгенерь запрос с 200 заголовками (например циклом в bash, добавляя -H "X-N: 1") - получишь 400.
  • Для финальной оценки прогони домен через securityheaders.com или Mozilla Observatory - цель A или A+.
Вот компактный security.conf-каркас целиком:

Код: Выделить всё

# globals/security.conf  -- подключать в http {}
server_tokens off;
max_headers 100;
client_max_body_size 10m;

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "no-referrer" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
add_header Content-Security-Policy "default-src 'self'; object-src 'none'; frame-ancestors 'self'; base-uri 'self'" always;
add_header X-XSS-Protection "0" always;   # 2026: legacy, держим в 0 (или вовсе убрать)

# HSTS -- ТОЛЬКО на server с listen 443 ssl, не здесь глобально!
# add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
Контрольные вопросы
  • Почему security-заголовки из server-контекста пропадают, если в location добавить хоть один свой add_header, и как этого избежать?
  • Зачем флаг always у add_header и на каких ответах заголовок появится без него?
  • Почему HSTS нельзя ставить на HTTP-сервере и чем опасна связка includeSubDomains + preload при кривых поддоменах?
  • Почему query-фильтры SQLi/XSS на nginx - это не WAF, и какие два способа их обойти ты можешь назвать?
Итог

Baseline-безопасность на прокси - это десяток директив, которые ставятся раз и работают на каждом запросе. Скрыл версию (server_tokens off, при наличии headers_more - перепиши Server), навесил security-заголовки с always (CSP - главный, X-Frame-Options и nosniff обязательны, HSTS осознанно на 443, X-XSS-Protection в 2026 - в 0 или в мусорку), ограничил методы и тело (limit_except, client_max_body_size), прикрыл флуд (max_headers), закрыл .git/.env/бэкапы regex-ом с исключением для .well-known. Грубые query-фильтры - бордюр, а не стена: настоящая защита от инъекций живёт на бэкенде и в WAF. Сделай это сегодня - и завтрашний массовый скан пройдёт мимо.
👍3 ❤️1 🔥4 😄 🤔2
Аватара пользователя
asynccoredump
Сообщения: 1
Зарегистрирован: 24 май 2026, 12:53

Re: Безопасность: заголовки, скрытие версии, ограничения

Сообщение asynccoredump »

Споткнулся ровно об описанную граблю: навесил заголовки в server, потом в одном location дописал add_header Cache-Control и весь security-набор там пропал. always не спасает от этого, спасает только то что заголовки в одном месте. Спасибо, теперь понятно почему.
👍 ❤️3 🔥1 😄 🤔
Аватара пользователя
FpgaGuru
Сообщения: 1
Зарегистрирован: 12 май 2026, 10:26

Re: Безопасность: заголовки, скрытие версии, ограничения

Сообщение FpgaGuru »

А есть смысл сначала катить CSP в Report-Only режиме на проде неделю? Боюсь сразу включить боевой и положить инлайн-аналитику и виджеты. И правильно понимаю, что frame-ancestors в CSP полностью заменяет X-Frame-Options, или старые браузеры всё равно требуют второй заголовок?
👍2 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
HTTP/2 и HTTP/3 (QUIC) в 2026
Следующая глава →
Rate limiting и защита от перегрузки

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: njs и динамические модули nginxБезопасность nginx: заголовки и hardeningRate limiting в nginx: защита от перегрузкиКонтроль доступа в nginx: auth и allow/denyЗаголовки в nginx: add_header и proxy_set_headerrealip в nginx: реальный IP за прокси и CDN

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя