Безопасность заголовков HTTP: настройка HSTS и CSP на бэкенде

Безопасность заголовков HTTP: настройка HSTS и CSP на бэкенде авг, 17 2026

Представьте ситуацию: вы запустили новый сервис, пользователи заходят, все работает. Но через неделю кто-то находит уязвимость в сторонней библиотеке или просто подменяет скрипт при переходе по ссылке. Бэкенд здесь ни при чем, но репутация страдает. Именно поэтому HSTS и CSP are механизмы защиты на уровне браузера, которые снижают риски перехвата трафика и внедрения вредоносного кода. Это не магия, а конкретные настройки, которые вы делаете один раз на сервере или в обратном прокси.

Многие разработчики считают эти заголовки «фронтендовской» задачей. Ошибка. Браузер слушает команды только от сервера (или промежуточного слоя). Если вы не отправите правильный ответ с нужными флагами, браузер будет работать в режиме «доверия», что опасно для любого публичного API или веб-приложения.

Почему HSTS критичен для вашего бэкенда

HTTP Strict Transport Security is заголовок, который заставляет браузер всегда использовать HTTPS, даже если пользователь случайно введет http://. Без него злоумышленник может перехватить запрос на этапе редиректа и подменить сертификат (MITM-атака).

Как это работает на практике:

  1. Клиент впервые обращается к вашему сайту по HTTPS.
  2. Сервер отвечает заголовком Strict-Transport-Security.
  3. Браузер запоминает этот факт на указанный срок (max-age).
  4. При следующих визитах браузер автоматически меняет протокол на HTTPS до отправки данных.

Здесь важно понимать атрибуты. Параметр max-age определяет время жизни правила в секундах. Для продакшена рекомендуется минимум год (31536000 секунд). Атрибут includeSubDomains расширяет действие правила на все поддомены. Это удобно, если у вас есть api.example.com и app.example.com - оба будут защищены одним правилом.

Рекомендуемые значения параметров HSTS Параметр Описание Рекомендация для Production max-age Срок действия в секундах 31536000 (1 год) includeSubDomains Применять к поддоменам Да (если все поддомены на HTTPS) preload Добавление в список preload Да (после проверки совместимости)

Нюанс с preload: если вы добавите этот флаг, ваш домен попадет в статический список браузеров. Убрать оттуда сложно. Поэтому включайте его только после того, как убедитесь, что все ресурсы доступны строго по HTTPS.

CSP: контроль над источниками кода

Content Security Policy is заголовок, который сообщает браузеру, откуда разрешено загружать скрипты, стили, шрифты и изображения. Это главный щит против XSS (Cross-Site Scripting) и инъекций.

По умолчанию браузеры довольно лояльны. Они позволяют исполнять инлайн-скрипты и подключать внешние CDN без ограничений. CSP меняет эту логику: вы сами решаете, какие источники доверять.

Базовая директива выглядит так: default-src 'self'. Это значит: «Загружай только с текущего домена». Но реальность сложнее. Вам нужно явно указать:

  • script-src: где брать JavaScript.
  • style-src: где брать CSS.
  • img-src: откуда грузить картинки.
  • connect-src: куда можно слать XHR/fetch запросы.

Если вы используете хеши для инлайн-скриптов (например, для мелких тултипов), используйте формат 'sha256-...'. Это безопаснее, чем разрешать 'unsafe-inline', которое фактически отключает защиту CSP для скриптов.

Где именно ставить эти заголовки?

Частый вопрос: «Ставить ли их в коде приложения или в Nginx?» Ответ зависит от архитектуры.

Если у вас монолитное приложение на Node.js, Go или Python, проще всего добавить заголовки в middleware или конфигурации фреймворка. Так вы гарантируете, что заголовок присутствует в каждом ответе, включая ошибки 404 или 500.

Но если у вас микросервисы, лучше централизовать настройку на уровне обратного прокси (Nginx, HAProxy, Traefik) или API Gateway. Почему? Потому что:

  • Не нужно менять код каждого сервиса.
  • Заголовки добавляются до того, как ответ покинет периметр безопасности.
  • Легче обновлять политику в одном месте.

В Nginx это делается строкой add_header. Важно помнить: add_header работает только для успешных ответов (2xx, 3xx), если не указано иное. Для ошибок часто требуется отдельная настройка или использование модулей.

Браузер отфильтровывает вредоносные скрипты через энергетический барьер

Типичные ошибки и как их избежать

Первая ошибка - слишком строгая политика на старте. Вы включаете CSP, запрещаете все внешние скрипты, и половина функций ломается. Решение: начинайте с режима report-only. Браузер не блокирует, а присылает отчет о том, что было бы заблокировано. Вы анализируете логи, исправляете политику, и только потом включаете блокировку.

Вторая ошибка - игнорирование CORS. Если ваш фронтенд ходит на другой домен (например, api.myapp.com), то connect-src в CSP должен включать этот домен. Иначе браузер заблокирует fetch-запросы, хотя сам запрос был бы валидным.

Третья ошибка - смешивание версий. Если вы обновляете версию JS-библиотеки, хеши в CSP становятся невалидными. Автоматизируйте генерацию хешей в CI/CD пайплайне, чтобы они всегда соответствовали текущему бандлу.

Практический пример конфигурации

Вот как может выглядеть минимальный, но рабочий набор заголовков для современного SPA:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; script-src 'self' 'sha256-abc123...'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://cdn.example.com; connect-src 'self' https://api.example.com
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff

Обратите внимание на X-Content-Type-Options: nosniff. Он запрещает браузеру гадать тип контента по содержимому файла, опираясь только на заголовок Content-Type. Это предотвращает выполнение HTML как JavaScript в некоторых старых браузерах.

Архитектура микросервисов с централизованной защитой на уровне прокси

Проверка перед деплоем

Никогда не деплойте новые политики CSP в прод без проверки. Используйте инструменты вроде browser devtools (вкладка Network -> Headers) или онлайн-сервисы аудита. Убедитесь, что:

  1. Все скрипты загружаются без ошибок в консоли.
  2. Нет сообщений "Blocked by CSP".
  3. Хеши совпадают с актуальными файлами.

Если вы работаете с командой, добавьте шаг в чеклист релиза: «Проверить наличие HSTS и CSP в ответах». Это займет минуту, но спасет часы отладки.

Частые вопросы

Нужен ли HSTS для внутреннего API без UI?

Да, если API доступен извне или используется мобильными клиентами. Даже если нет браузера, HSTS защищает TLS-сессию от деградации до HTTP. Для чисто внутренних сервисов внутри Docker-сети это менее критично, но хорошая практика.

Что делать, если CSP блокирует легитимный скрипт?

Сначала проверьте, нужен ли этот скрипт вообще. Если да, добавьте его источник в script-src. Если это инлайн-код, посчитайте SHA256 хеш и добавьте его в директиву. Избегайте использования 'unsafe-inline', если возможно.

Какой порядок важности заголовков?

HSTS важнее для базовой защиты транспорта. CSP важнее для защиты от XSS. Оба должны быть настроены одновременно. Порядок добавления в конфиге не имеет значения, главное - наличие в каждом HTTP-ответе.

Работают ли эти заголовки на WebSocket?

HSTS влияет на начальный handshake (WebSocket поверх WSS требует HTTPS). CSP напрямую не контролирует WebSocket соединения, но connect-src может ограничивать URL, на которые можно открыть WS-соединение в некоторых реализациях браузеров.

Стоит ли использовать nonce вместо хешей?

Nonce (одноразовый идентификатор) удобнее для динамически генерируемого HTML, потому что не нужно пересчитывать хеши при каждом сборке. Однако nonce усложняет кэширование страниц. Для статических SPA хеши предпочтительнее.