HTTP security headers — простой и эффективный слой защиты, который часто остаётся «на потом». Пара корректно выставленных заголовков может отрезать целые классы атак: XSS через запрет инлайна, кликджекинг через запрет встраивания, утечки referrer-данных и злоупотребления чувствительными API браузера. В этой статье собрал практические пресеты и типовые сценарии для Nginx и Apache, чтобы вы могли внедрить заголовки быстро и без сюрпризов в проде.
Что именно дают эти заголовки
В статье сосредоточимся на четырёх ключевых заголовках безопасности и их роли:
Content-Security-Policy
(CSP) — ограничивает откуда можно загружать скрипты, стили, изображения, шрифты, а также управляет встраиваемыми фреймами и смешанным контентом. Главный антагонист XSS и произвольных внешних подключений.Permissions-Policy
— контролирует доступ страницы и её фреймов к возможностям браузера: геолокации, камере, микрофону, акселерометру, полноэкранному режиму и др.Referrer-Policy
— что именно утекает в заголовкеReferer
при переходах на другие домены. Важен для приватности пользователей и скрытия структуры URL.X-Frame-Options
— старый, но ещё полезный способ защититься от встраивания страницы в чужой сайт (кликджекинг). Сегодня дублируется и тоньше настраивается директивойframe-ancestors
в CSP.
Эти заголовки — про «умолчания в сторону безопасности». Они задают жёсткие рамки, и если нужно — точечно расширяются. Главное — не наоборот.
Где и как добавлять заголовки: основы для Nginx и Apache
Nginx
Ключевые моменты:
- Используйте
add_header ... always;
, чтобы заголовки не исчезали на редиректах и ошибках. - Старайтесь задавать заголовки в
server
-блоке; если у вас разные политики для поддиректорий, используйтеlocation
с явными переопределениями. - Помните:
add_header
переопределяется в более узком контексте. Если вlocation
заданadd_header
, заголовки изserver
безalways
могут пропасть.
Apache
Ключевые моменты:
- Нужен модуль
mod_headers
. ИспользуйтеHeader always set
для ошибок и внутренних редиректов. - Для тонкой настройки на уровне каталогов можно применять
.htaccess
(учтите накладные расходы), либо конфиг<Directory>
/<VirtualHost>
. На виртуальном хостинге чаще всего доступен.htaccess
, на VDS — полный контроль в конфиге. - При наличии обратных прокси следите, чтобы заголовки не дублировались и не конфликтовали с заголовками от апстрима.

Content‑Security‑Policy (CSP): стратегия и пресеты
CSP — самый мощный и самый «ломающий» заголовок. С него всегда начинаем в режиме отчётов, а уже потом включаем enforcement.
Принципы
- Базовая философия: «запретить всё, потом разрешить необходимое». Стартуем с
default-src 'self'
и постепенно расширяем. - Минимизируйте использование
'unsafe-inline'
и'unsafe-eval'
. Для инлайна применяйтеnonce-
илиsha256-
/sha384-
хеши. frame-ancestors
в CSP заменяетX-Frame-Options
и гибче его. Для поддержки старых браузеров оставляйте оба.upgrade-insecure-requests
помогает автоматически апгрейдить HTTP-ресурсы до HTTPS и устранить смешанный контент.
Важные директивы
default-src
— общий «белый список» источников.script-src
,style-src
,img-src
,font-src
,connect-src
— управление каналами загрузки кода, медиа и сетевых запросов (XHR/Fetch/WebSocket).frame-src
иchild-src
— куда можно встраивать фреймы/воркеры (в актуальных политиках используйтеframe-src
).frame-ancestors
— кто может встраивать вашу страницу как фрейм.object-src
— лучше'none'
, чтобы отключить устаревшие плагины.base-uri
— ограничивает<base>
, предотвращая подмену базового URL.form-action
— контролирует, куда можно отправлять формы.
Как безопасно внедрять CSP
- Включите
Content-Security-Policy-Report-Only
с максимально жёсткими правилами, но без блокировок. - Соберите отчёты о нарушениях и логи браузера (DevTools → вкладки Network/Security/Console).
- Поправьте политику, добавив необходимые источники; уберите лишние.
- Переключите на боевой
Content-Security-Policy
. Для критичных зон (админки) можно оставить Report-Only параллельно ещё на некоторое время.
Генерация nonce должна быть на уровне приложения и для каждого ответа. Не используйте предсказуемые значения. Для статических сайтов разумнее переходить на хеши.
Пример строгой CSP для типового сайта
Эта политика подходит как отправная точка для большинства сайтов без внешних CDN и без нужды встраиваться в чужие фреймы:
# Nginx (внутри server):
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests" always;
# Apache (внутри VirtualHost или .htaccess при включённом mod_headers):
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests"
Комментарии по пресету:
img-src 'self' data:
иfont-src 'self' data:
разрешают встраиваемые data-URL для SVG/favicon/woff2, что часто необходимо.frame-ancestors 'none'
запрещает встраивание — хорошо против кликджекинга, но сломает виджеты и предпросмотры в админках, если они требуют фреймов.upgrade-insecure-requests
«подтягивает» HTTP-ресурсы к HTTPS, что помогает при миграции. Убедитесь, что на домене установлены корректные SSL-сертификаты. Если вы в процессе переезда на HTTPS и HSTS, пригодится наш разбор: миграция домена, 301, HSTS и SSL.
Добавляем исключения: CMS/WordPress, SPA, внешние CDN
Частые расширения:
- Для CMS, где инлайн-скрипты/стили не убрать, временно добавляют
'unsafe-inline'
кscript-src
/style-src
. Лучше заменить инлайн на файлы или внедритьnonce
/hash
. - SPA и аналитика используют внешние домены в
connect-src
(API, WebSocket). Их нужно перечислить явно. - CDN для статики потребует добавить домены в соответствующие директивы (
script-src
,style-src
,img-src
,font-src
). - Если нужно, чтобы страницу можно было встраивать на ваш поддомен, используйте
frame-ancestors 'self'
вместо'none'
.
Report‑Only для безопасной миграции
Подготовительный режим без блокировок:
# Nginx:
add_header Content-Security-Policy-Report-Only "default-src 'self'; ..." always;
# Apache:
Header always set Content-Security-Policy-Report-Only "default-src 'self'; ..."
Сбор отчётов можно организовать на собственную эндпоинт-точку приложения. Учитывайте, что report-uri
устаревает в пользу report-to
, но первый по-прежнему широко поддерживается. Не включайте в отчёты персональные данные.
Permissions‑Policy: закрываем доступ к лишним API
Permissions-Policy
пришёл на смену Feature-Policy
. Он позволяет включать/выключать возможности браузера для документа и вложенных фреймов. Синтаксис: feature=(...)
, где в скобках список источников: "self"
, конкретные источники или пустые ()
для полного запрета. *
разрешает всем.
Базовый пресет «запретить всё лишнее»
# Nginx:
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), accelerometer=(), gyroscope=(), magnetometer=(), usb=(), bluetooth=(), payment=(self), fullscreen=(self)" always;
# Apache:
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), accelerometer=(), gyroscope=(), magnetometer=(), usb=(), bluetooth=(), payment=(self), fullscreen=(self)"
Пояснения:
- Все рискованные датчики отключены. Это снижает поверхность атаки для встраиваемых фреймов третьих сторон.
fullscreen
иpayment
оставлены для самого сайта ((self)
), что обычно безопасно.- Если ваш плеер требует
autoplay
или сайт —clipboard-read
/clipboard-write
, добавьте их точечно.
Referrer‑Policy: приватность и аналитика
Рекомендуемое значение по умолчанию — strict-origin-when-cross-origin
. Оно отправляет полный URL только в пределах того же происхождения, а наружу — только схему+хост+порт. Часто это золотая середина между приватностью и полезностью для аналитики.
# Nginx:
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Apache:
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Если данные реферера вообще не нужны, используйте no-referrer
. Для особо чувствительных разделов (например, страницы с токенами в URL) можно локально ужесточить политику через отдельный location
/Directory
.
X‑Frame‑Options: наследие, которое ещё работает
X-Frame-Options
поддерживается старыми браузерами и полезен как дополнительная страховка к frame-ancestors
в CSP. Два практичных значения:
DENY
— полностью запрещает встраивание.SAMEORIGIN
— разрешает встраивание только с того же происхождения.
# Nginx:
add_header X-Frame-Options "DENY" always;
# Apache:
Header always set X-Frame-Options "DENY"
ALLOW-FROM
устарел и поддерживается непоследовательно. Для точечного разрешения отдельных источников используйте frame-ancestors
в CSP.
Комбинируем всё вместе: минимальный надёжный набор
Соберём аккуратный комплект, который можно включить для большинства сайтов и затем расширять по мере необходимости:
# Nginx (server):
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests" always;
add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), accelerometer=(), gyroscope=(), magnetometer=(), usb=(), bluetooth=(), payment=(self), fullscreen=(self)" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "DENY" always;
# Apache (VirtualHost):
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-src 'none'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'; upgrade-insecure-requests"
Header always set Permissions-Policy "geolocation=(), camera=(), microphone=(), accelerometer=(), gyroscope=(), magnetometer=(), usb=(), bluetooth=(), payment=(self), fullscreen=(self)"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-Frame-Options "DENY"
Проверка и отладка
- Проверьте заголовки через команду:
curl -I https://ваш-домен
. Убедитесь, что заголовки видны и на 200, и на 301/404 (для Nginx/Apache мы использовалиalways
). - Откройте DevTools браузера: вкладка Network → выберите документ → вкладка Headers; вкладка Security подскажет проблемы со смешанным контентом; Console покажет нарушения CSP.
- Отслеживайте, не дублируются ли заголовки (часто при сочетании CDN/прокси с origin-сервером).
Переключатели по зонам и статике
Нередко политика для админки и публичной части различается. Например, админке нужен фрейм для визуального редактора или предпросмотра. Решение — сделать локальный override:
# Nginx (пример для /admin/ разрешаем фреймы с self):
location /admin/ {
add_header Content-Security-Policy "default-src 'self'; ...; frame-ancestors 'self'" always;
}
# Apache (Directory):
<Directory "/var/www/site/admin">
Header always set Content-Security-Policy "default-src 'self'; ...; frame-ancestors 'self'"
</Directory>
Ещё один частый кейс — статика, которую отдаёт другой сервер/поддомен. Добавьте его в img-src
/font-src
/style-src
/script-src
, иначе получите блокировки в консоли браузера.
Типовые сценарии и подсказки
Статический сайт или документация
Чаще всего достаточно строгого дефолтного набора. Если генератор статического сайта встраивает инлайн-скрипты для поиска/темизации, рассмотрите переход на хеши. Для примера, разрешите конкретный инлайн-скрипт хешем:
# Фрагмент для CSP со скрипт-хешем (пример SHA-256):
add_header Content-Security-Policy "script-src 'self' 'sha256-...'; style-src 'self'; ..." always;
CMS (WordPress, 1C‑Битрикс, Joomla)
Готовьтесь к множеству инлайн-скриптов/стилей и внешних ресурсов. Мягкий путь миграции — Report-Only
+ постепенное вырезание инлайна в шаблонах. Если без инлайна никак, внедряйте nonce
на уровне приложения и добавляйте script-src 'nonce-...'
в CSP. Для административной зоны может понадобиться frame-ancestors 'self'
.
SPA/приложение
Особое внимание к connect-src
(API и WebSocket), worker-src
(если используете Web Worker), а также к frame-src
для сторонних виджетов (платёжные шлюзы, карты). Заранее составьте список доменов-провайдеров и пропишите их в соответствующих директивах.
Подводные камни
- Инлайн в сторонних виджетах. Если вставляете внешний виджет через
<iframe>
, ваша CSP не контролирует содержимое фрейма, ноPermissions-Policy
иReferrer-Policy
настраивают ограничения для фрейма со стороны контейнера. Для контроля скриптов внутри фрейма потребуется политика на стороне поставщика. - Смешанный контент.
upgrade-insecure-requests
поможет, но если внешний ресурс недоступен по HTTPS, нужно заменить источник или отказаться от ресурса. - Кэширование. Промежуточные кэши/CDN могут переопределять или отбрасывать заголовки. Проверьте, что на всех слоях политика одинакова.
- Дублирование заголовков. Два одинаковых заголовка могут конфликтовать. Держите политику в одном месте: либо на CDN, либо на origin, либо добейтесь идентичности.
- Совместимость. Старые браузеры игнорируют часть директив CSP и
Permissions-Policy
. Именно поэтому полезно держатьX-Frame-Options
параллельно сframe-ancestors
, а также включать «усилители» вродеX-Content-Type-Options: nosniff
вне темы этой статьи.
Практическая методика внедрения в прод
- Инвентаризация ресурсов. Соберите список доменов для скриптов, стилей, шрифтов, изображений, соединений. Зафиксируйте особые места: фреймы, платежные страницы, загрузчики файлов.
- Включите Report‑Only. Поставьте жёсткую политику CSP в Report-Only, разгрузите логи по типам нарушений.
- Итерации. Разрешайте необходимые источники точечно. По возможности убирайте инлайн в кодовую базу.
- Включение Enforcement. Переведите на боевую CSP, оставив Report-Only рядом на неделю‑две для мониторинга хвостов.
- Разделение зон. Там, где нужно, задайте локальные ослабления (админка, сервисные пути). Лучше ослаблять локально, а не глобально.
- Автоматические тесты. Добавьте проверки заголовков в CI/CD:
curl -I
по ключевым URL и проверка значений через скрипты. Если поднимаете новый сервер, посмотрите сравнение панелей для VDS.
FAQ по типовым сбоям
- «Сломались карты/видео/виджет оплаты». Добавьте источник провайдера в
frame-src
и при необходимости разрешите соответствующиеconnect-src
/script-src
. ПроверьтеPermissions-Policy
, возможно требуетсяfullscreen
илиpayment
. - «Инлайн-скрипты заблокированы». Временно можно
'unsafe-inline'
, но лучше внедритьnonce
или хеши и поэтапно убрать инлайн. - «Referrer стал пустой и поломалась аналитика». Убедитесь, что выбрали подходящую политику.
strict-origin-when-cross-origin
обычно достаточно информативна и безопасна. - «Страница не открывается во фрейме партнёра». Проверьте
frame-ancestors
иX-Frame-Options
. Возможно, нужно ослабить политику для конкретного роута.
Итоги
Грамотно выставленные security headers — это быстрый апгрейд безопасности, заметный для сканеров и реальных атак. Начните с консервативного базового пресета: CSP с default-src 'self'
, запрет встраивания, строгую политику referrer и жёсткий Permissions-Policy
. Затем постепенно открывайте ровно то, что действительно нужно вашему приложению. Двигайтесь итеративно и проверяйте поведение в проде: так защита не ударит по функциональности.