// Engineering Log

HTTP, HTTPS и TLS: Часть 2 — Заголовки HTTP: какие бывают и за что отвечают

Опубликовано 28.09.2026

// Быстрый маршрут

Эта статья относится к теме Безопасность и защита.

Заголовки — это метаданные HTTP-сообщения: строки вида Имя: значение между стартовой строкой и телом. По ним сервер понимает, какой сайт нужен, в каком формате и на каком языке отдать ответ, кто перед ним и есть ли у клиента свежая копия. Браузер по заголовкам ответа решает, как показать содержимое, можно ли его кешировать и какие ограничения безопасности действуют на странице.

Ниже разобраны заголовки, которые встречаются в работе постоянно, по группам. Посмотреть их у любого сайта можно командой:

bash
curl -sI https://example.ru/          # только заголовки ответа (метод HEAD)
curl -sv -o /dev/null https://example.ru/ 2>&1 | grep -E '^[<>]'   # запрос и ответ

Как устроены заголовки

  • Имена не зависят от регистра. В HTTP/2 и HTTP/3 они всегда строчные, и в журналах вы увидите content-type, а не Content-Type.
  • Один заголовок может встречаться несколько раз, значения при этом объединяются через запятую. Исключение — Set-Cookie: каждая cookie идёт отдельной строкой.
  • Префикс X- когда-то обозначал нестандартные заголовки. RFC 6648 (2012) рекомендует от него отказаться, но старые имена вроде X-Forwarded-For остались в ходу.
  • Размер заголовков ограничен сервером. В Nginx буфер под строку запроса и заголовок задают large_client_header_buffers (по умолчанию 4 буфера по 8 КБ); если cookie разрослись, сервер ответит 400 Request Header Or Cookie Too Large.

Заголовки запроса

Host — имя сайта и порт, если он не стандартный. На одном IP-адресе могут жить сотни сайтов, и веб-сервер выбирает нужный именно по Host (в Nginx — по server_name). Это единственный обязательный заголовок HTTP/1.1. В HTTP/2 и HTTP/3 его роль играет псевдозаголовок :authority.

User-Agent — кто делает запрос: браузер и его версия, curl/8.7.1, python-requests/2.32. Серверы используют его для статистики и иногда для блокировки ботов. Значение заполняет сам клиент, поэтому подделать его может кто угодно; более надёжные признаки клиента — отпечатки TLS, о них — «Что такое JA3 и JA4».

Accept, Accept-Language, Accept-Encoding — согласование содержимого. Клиент перечисляет, что он умеет принимать, с весами q:

Accept: text/html,application/xhtml+xml,*/*;q=0.8
Accept-Language: ru-RU,ru;q=0.9,en;q=0.8
Accept-Encoding: gzip, deflate, br, zstd

Сервер выбирает вариант и сообщает о выборе в Content-Type, Content-Language и Content-Encoding. Если ответ зависит от одного из этих заголовков, сервер должен добавить Vary: Accept-Encoding (или другое имя), иначе прокси и CDN отдадут сжатую версию клиенту, который её не понимает.

Authorization — учётные данные. Basic — логин и пароль в Base64 (это кодировка, а не шифрование: без HTTPS пароль читается как есть). Bearer — токен, например JWT или ключ API.

Cookie — cookie, которые браузер сохранил для этого сайта: Cookie: session=abc123; lang=ru.

Referer — адрес страницы, с которой пришёл пользователь (слово написано с ошибкой ещё в первой спецификации, так и осталось). Сколько в нём передавать, определяет заголовок ответа Referrer-Policy; по умолчанию браузеры отправляют на другие сайты только домен.

Origin — схема, хост и порт страницы, откуда отправлен запрос. Браузер добавляет его к запросам с другого сайта и к POST; на нём основаны CORS и проверка против CSRF.

If-None-Match, If-Modified-Since — условные запросы. Браузер сообщает, какая версия у него уже есть, и сервер отвечает коротким 304 Not Modified без тела, если ничего не изменилось.

Range — запрос части файла: Range: bytes=1000000-. На нём работают докачка и перемотка видео.

Content-Type, Content-Length — тип и длина тела запроса. Для форм — application/x-www-form-urlencoded или multipart/form-data, для API — обычно application/json. Частая ошибка: JSON отправлен без Content-Type: application/json, и приложение не видит данных.

Заголовки ответа о содержимом

Content-Type — что лежит в теле и в какой кодировке: text/html; charset=utf-8, application/json, image/webp. Браузер решает, как показать ответ, именно по нему. Чтобы браузер не пытался угадать тип сам (это приводило к выполнению загруженных пользователями файлов как скриптов), добавляют X-Content-Type-Options: nosniff.

Content-Encoding — чем сжато тело: gzip, br (Brotli), zstd.

Content-Disposition — показать файл в браузере (inline) или предложить скачать (attachment; filename="report.pdf").

Location — адрес для перенаправления в ответах 3xx и адрес созданного ресурса в ответе 201 Created.

Server — программа веб-сервера. Версию лучше не раскрывать: в Nginx — server_tokens off;.

Кеширование

Кеш есть у браузера, у CDN и у прокси. Правила для них задаёт сервер.

Cache-Control — главный заголовок кеширования:

  • max-age=3600 — ответ свежий один час, в течение этого времени браузер берёт его из кеша, не спрашивая сервер;
  • s-maxage=600 — то же для общих кешей (CDN, прокси), приоритетнее max-age;
  • no-cache — хранить можно, но перед каждым использованием проверять у сервера (через If-None-Match);
  • no-store — не сохранять вообще; для страниц с личными данными;
  • private — только браузер, CDN хранить не должен; для страниц, зависящих от пользователя;
  • public — можно хранить в общих кешах;
  • immutable — ресурс никогда не изменится; ставят на файлы с хешем в имени (app.3f9a1c.js) вместе с большим max-age.

Название no-cache сбивает с толку: оно не запрещает кеширование. Запрещает no-store.

ETag — идентификатор версии ресурса (например, хеш содержимого). Браузер возвращает его в If-None-Match. Last-Modified — дата изменения, возвращается в If-Modified-Since.

Age — сколько секунд ответ пролежал в кеше CDN или прокси. Если Age есть, ответ пришёл не от вашего сервера. CDN часто добавляют и собственные заголовки: CF-Cache-Status: HIT у Cloudflare, X-Cache: HIT у многих других.

Сервер устанавливает cookie заголовком Set-Cookie, по одному на каждую:

Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400
  • Secure — отправлять только по HTTPS;
  • HttpOnly — недоступна из JavaScript, её не украдёт внедрённый скрипт;
  • SameSite=Lax — не отправлять при запросах с чужих сайтов, кроме обычного перехода по ссылке; Strict — никогда с чужих сайтов; None — всегда (только вместе с Secure). Современные браузеры без явного указания считают cookie Lax;
  • Max-Age или Expires — срок жизни; без них cookie удаляется при закрытии браузера;
  • Domain — если указан, cookie уходит и на поддомены. Без него — только на тот хост, который её установил.

Для cookie сессии разумный минимум — Secure; HttpOnly; SameSite=Lax.

CORS

Браузер не даёт JavaScript со страницы https://app.example.ru читать ответы с https://api.example.ru, если сервер API явно не разрешил. Это правило одного источника (same-origin policy), а CORS — механизм разрешений:

Access-Control-Allow-Origin: https://app.example.ru
Access-Control-Allow-Credentials: true

Для «непростых» запросов — с методами PUT и DELETE, с Content-Type: application/json, с заголовком Authorization — браузер сначала отправляет предварительный запрос OPTIONS с заголовками Access-Control-Request-Method и Access-Control-Request-Headers. Сервер отвечает, что разрешено (Access-Control-Allow-Methods, Access-Control-Allow-Headers, Access-Control-Max-Age), и только потом уходит настоящий запрос.

Важные детали:

  • CORS защищает пользователя браузера, а не сервер. curl и любые скрипты вне браузера CORS не проверяют;
  • Access-Control-Allow-Origin: * не работает вместе с cookie и Authorization — для запросов с учётными данными нужен конкретный источник;
  • если ошибка CORS видна в консоли, сначала посмотрите ответ на OPTIONS: часто его блокирует аутентификация или сервер отвечает на него 404/405.

Заголовки безопасности

  • Strict-Transport-Security (HSTS) — «ходи на этот сайт только по HTTPS»: max-age=31536000; includeSubDomains. Подробнее — «Переход на HTTPS: редиректы, HSTS, HTTPS-First».
  • Content-Security-Policy — откуда странице разрешено загружать скрипты, стили, картинки, куда отправлять формы. Главная защита от XSS. Пример: default-src 'self'; img-src 'self' data:; frame-ancestors 'none'. Начинать удобно с Content-Security-Policy-Report-Only: браузер сообщает о нарушениях в консоль, но ничего не блокирует.
  • X-Frame-Options: DENY или директива CSP frame-ancestors — запрет встраивать сайт во фрейм на чужих страницах (защита от кликджекинга).
  • X-Content-Type-Options: nosniff — не угадывать тип содержимого.
  • Referrer-Policy: strict-origin-when-cross-origin — не отдавать чужим сайтам полный адрес страницы.
  • Permissions-Policy — какие возможности браузера доступны странице: camera=(), microphone=(), geolocation=().

Заголовок X-XSS-Protection устарел: современные браузеры его игнорируют, вместо него используется CSP.

В Nginx заголовки ответа добавляются директивой add_header, и у неё есть ловушка: если в блоке location есть хотя бы один add_header, заголовки с уровня server в нём не действуют. Параметр always нужен, чтобы заголовок отдавался и в ответах с ошибками:

nginx
server {
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

Заголовки прокси

Когда перед приложением стоит Nginx, HAProxy или CDN, приложение видит соединение от прокси, а не от клиента. Исходные данные прокси передаёт в заголовках:

  • X-Forwarded-For: 192.0.2.7, 192.0.2.2 — цепочка адресов: клиент и все прокси по пути;
  • X-Forwarded-Proto: https — по какому протоколу пришёл клиент; без него приложение за прокси, принимающим HTTPS, считает, что работает по HTTP, и строит неверные ссылки или уходит в бесконечный редирект на HTTPS;
  • X-Forwarded-Host — исходный Host;
  • X-Real-IP — адрес клиента одним значением (соглашение Nginx);
  • Forwarded: for=192.0.2.7;proto=https — стандартная замена всем перечисленным (RFC 7239), поддерживается реже.

Этим заголовкам можно доверять, только если их выставил ваш прокси. Клиент может отправить X-Forwarded-For сам, и приложение, которое берёт первый адрес из списка, получит подделку. Правило: берите адрес, добавленный последним доверенным прокси, а в Nginx задайте доверенные сети явно:

nginx
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

Служебные заголовки соединения

Connection, Keep-Alive, Transfer-Encoding, Upgrade относятся к одному участку пути — между клиентом и ближайшим прокси — и дальше не передаются. В HTTP/2 и HTTP/3 они запрещены. Поэтому, чтобы проксировать WebSocket через Nginx, Upgrade и Connection приходится передавать к приложению явно — пример в статье «Переход на HTTPS и заголовок Upgrade».

Типичные ошибки

  • Cache-Control не задан — браузеры и CDN кешируют по эвристике (часто 10 % от возраста Last-Modified), и пользователи видят старые стили после выкладки.
  • no-cache вместо no-store на страницах с персональными данными.
  • Ответ зависит от cookie или языка, а Vary не выставлен и нет private — CDN отдаёт одному пользователю страницу другого.
  • Access-Control-Allow-Origin берётся из Origin запроса без проверки по списку — это то же самое, что разрешить всем, но с cookie.
  • Приложение доверяет X-Forwarded-For от любого клиента — обходится ограничение по IP и подделываются журналы.

// Похожая задача

Если у вас похожая ситуация

Эта статья относится к одной из рабочих тем. Можно продолжить чтение по теме, перейти на главную, чтобы понять, чем я занимаюсь, или сразу открыть услуги.

Тема статьи

Безопасность и защита

SSL, hardening, доступы, защита сервисов и безопасные конфигурации.

Часто с этим приходят

  • Настроить SSL, сертификаты и безопасные подключения
  • Ограничить доступы и закрыть лишние точки входа
  • Усилить конфигурацию сервера и сервисов

// Следующий шаг

Если вам нужна не только статья, а помощь по этой теме, удобнее сразу перейти в услугу. Главная и подборка материалов остаются рядом.

Открыть услуги

// Contact

Нужна помощь?

Свяжись со мной и я помогу решить проблему

Написать в Telegram

Отвечаю в течение рабочего дня (03:00–13:00 GMT)

Или оставьте заявку здесь:

Подтвердите, что вы не бот.

Написать и получить быстрый ответ