Безопасные заголовки в Nginx: рабочие приёмы
Одной строкой в конфигурации Nginx можно закрыть часть атак через браузер, но случайный CSP способен сломать скрипты, платежи и аналитику. Разбираем базовый набор заголовков, порядок внедрения и ошибки, которые встречаются в проде.

Безопасные заголовки в Nginx не требуют отдельного сервиса, но неправильный Content-Security-Policy может за один деплой отключить JavaScript, шрифты и оплату. Ниже — практическая схема для nginx Open Source 1.25–1.27: что включить сразу, что проверять в браузере и какие политики нельзя копировать вслепую.
Начните с базового набора
Заголовки добавляют в блок server, если правило относится ко всему виртуальному хосту. Параметр always нужен, чтобы Nginx добавлял заголовок не только к ответам 2xx и 3xx, но и к ошибкам. Это важно: браузер должен получать защитные ограничения и при ответе 404 или 500.
server {
listen 443 ssl;
server_name example.com;
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=(), camera=(), microphone=()" always;
# Остальные настройки сайта и SSL находятся ниже.
}
X-Content-Type-Options: nosniff запрещает браузеру угадывать MIME-тип ресурса. Если сервер отдал JavaScript как text/plain, браузер не должен превращать его в исполняемый скрипт.
X-Frame-Options: SAMEORIGIN не позволяет встраивать страницу во фрейм с другого origin. Если сайту вообще не нужны фреймы, более жёсткий вариант — DENY. Если встраивание требуется партнёрам, этот заголовок нельзя настраивать механически: список разрешённых источников лучше задавать через CSP.
Referrer-Policy: strict-origin-when-cross-origin оставляет полный путь внутри своего origin, но при переходе на другой origin отправляет только схему, хост и порт. Так URL с идентификаторами или параметрами реже утекает во внешнюю аналитику.
Permissions-Policy отключает браузерные возможности, которые сайту не нужны. Синтаксис и поддержка отдельных функций зависят от браузера, поэтому список проверяйте по фактическим сценариям сайта, а не по шаблону из чужой конфигурации.
Самый безопасный заголовок — тот, который вы проверили на страницах с авторизацией, оплатой, загрузкой файлов и сторонними скриптами.
Термин: origin — сочетание схемы, домена и порта. Например, https://example.com и https://cdn.example.com — разные origin, даже если домены принадлежат одной компании.
HSTS: включайте только после проверки HTTPS
Strict-Transport-Security заставляет браузер обращаться к домену по HTTPS. После получения политики браузер сам заменяет HTTP-ссылки на HTTPS в течение указанного срока. Это защищает от понижения соединения до HTTP, но одновременно делает ошибку с сертификатом или поддоменом болезненной.
server {
listen 443 ssl;
server_name example.com;
add_header Strict-Transport-Security "max-age=31536000" always;
}
Сначала проверьте, что HTTPS работает на всех путях, нет смешанного контента, а HTTP перенаправляет на HTTPS. Параметр includeSubDomains добавляйте только после проверки каждого поддомена: иначе тестовый сервис или почта на отдельном имени могут перестать открываться. Параметр preload — отдельное обязательство, а не «усиленная галочка»: перед ним нужно изучить актуальные требования списка HSTS Preload.
Стройте CSP от реальных зависимостей
Content-Security-Policy ограничивает источники, откуда браузер может загружать скрипты, стили, изображения, шрифты и соединения. Это не универсальный фильтр XSS, а декларация разрешений. Для сайта с inline-скриптами, рекламой, картами и платежным виджетом готовая строка default-src 'self' почти наверняка что-то сломает.
Сначала режим наблюдения
Перед блокировкой используйте Content-Security-Policy-Report-Only. Браузер покажет нарушения в инструментах разработчика, но не станет запрещать загрузку. Так вы увидите реальные CDN, домены аналитики и внешние API.
server {
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' https://cdn.example.net; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.example.net; connect-src 'self' https://api.example.net; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;
}
В примере домены условные: замените их на источники, которые действительно использует приложение. Не добавляйте https: в script-src без необходимости. Такая запись разрешит скрипты с любого HTTPS-хоста и ослабит смысл политики.
Затем переходите к блокировке
После проверки замените имя заголовка на Content-Security-Policy. Для нового проекта разумно начать с минимальной политики:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;
Директива frame-ancestors управляет тем, кто может встроить страницу, и обычно лучше подходит для этой задачи, чем один X-Frame-Options. base-uri 'self' ограничивает источник для HTML-тега base, а form-action 'self' не позволяет отправлять формы на произвольный домен.
Не начинайте с 'unsafe-inline' и 'unsafe-eval', если приложение можно перестроить без них. Для inline-скриптов современный путь — nonce или хэш, но nonce должен генерироваться заново для каждого ответа и передаваться в HTML; статическое значение в конфигурации Nginx не решает задачу.
Сравните варианты перед внедрением
| Подход | Что защищает | Цена и риск | Когда выбирать |
|---|---|---|---|
| Базовые заголовки | MIME-sniffing, часть clickjacking, лишние браузерные API | Низкий риск поломки, нужно проверить фреймы | Практически для любого HTTPS-сайта |
| HSTS без поддоменов | Переходы с HTTPS на HTTP | Нельзя быстро вернуться к HTTP после кеширования политики | Когда весь основной домен стабильно работает по HTTPS |
| CSP в Report-Only | Показывает нарушения загрузки ресурсов | Ничего не блокирует, отчёты нужно анализировать | Перед первой строгой политикой |
| Строгий CSP | Снижает последствия XSS и несанкционированных загрузок | Высокий риск сломать сторонние скрипты и inline-код | После инвентаризации всех зависимостей |
Что ломается в реальной конфигурации
- Заголовок виден не на всех ответах. Проверьте наличие
always. Затем посмотрите, не перекрывает ли вложенныйlocationнаследование: если в нём есть собственныйadd_header, родительские заголовки могут не унаследоваться. - CSP заблокировал ресурс. Откройте Console в браузере и найдите директиву, которая нарушена. Добавляйте конкретный origin в конкретную директиву, а не ослабляйте всю политику до
*. - Не работает iframe. Проверьте одновременно
X-Frame-Optionsиframe-ancestors. Также ограничение может находиться в заголовках самой страницы, которую вы пытаетесь встроить. - HSTS создал проблемы на поддомене. Не добавляйте
includeSubDomains, пока не проверены все имена. Для тестовой среды используйте отдельный домен, а не тот, на котором уже опубликована строгая политика. - Заголовок не появился после изменения. Проверьте синтаксис и конфигурацию командой
nginx -t, затем перечитайте конфигурацию без остановки процесса. Ответ мог прийти из CDN или другого reverse proxy, а не из этого Nginx.
Как проверить результат
Проверяйте не только главную страницу, но и HTML после редиректа, страницу ошибки, API-ответ, статический файл и страницу с авторизацией. Для быстрой проверки заголовков используйте запрос к конкретному HTTPS-адресу:
curl -I https://example.com/
В браузере дополнительно откройте DevTools, вкладку Network и посмотрите финальный ответ после всех редиректов. Вкладка Console нужна для нарушений CSP. Онлайн-аудиторы полезны как дополнительная проверка, но не заменяют тестирование бизнес-сценариев: они не знают, нужен ли вашему магазину конкретный платёжный iframe.
Что делать дальше
Сначала внедрите базовые заголовки и проверьте их на всех типах ответов. Затем включите CSP в Report-Only минимум на полный цикл релизов, соберите список источников и уберите лишние зависимости. В проде следите за ошибками CSP, отказами загрузки ресурсов и долей страниц, которые отдаются без HTTPS. Инвалидация кеша CDN и настройка cookie-флагов Secure, HttpOnly и SameSite — связанные, но отдельные задачи.
Антиблок-аналитик 115-ФЗ: Анализ выписок, поиск рисков блокировки счёта, инструкции по безопасным транзакциям.
Спросить за 41 ₽Источники: NGINX documentation: HTTP headers module, MDN Web Docs: Content Security Policy
Частые вопросы
Можно ли просто вставить готовый CSP из примера?
Нужно ли добавлять X-XSS-Protection?
Почему add_header не срабатывает для ошибки 404?
Можно ли включить HSTS сразу с includeSubDomains и preload?
Что делать, если после CSP перестала работать аналитика?
Нужно ли настраивать CORS вместе с безопасными заголовками?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.