// Engineering Log
HTTP, HTTPS и TLS: Часть 2 — Заголовки HTTP: какие бывают и за что отвечают
Опубликовано 28.09.2026
// Быстрый маршрут
Эта статья относится к теме Безопасность и защита.
Заголовки — это метаданные HTTP-сообщения: строки вида Имя: значение между стартовой строкой и телом. По ним сервер понимает, какой сайт нужен, в каком формате и на каком языке отдать ответ, кто перед ним и есть ли у клиента свежая копия. Браузер по заголовкам ответа решает, как показать содержимое, можно ли его кешировать и какие ограничения безопасности действуют на странице.
Ниже разобраны заголовки, которые встречаются в работе постоянно, по группам. Посмотреть их у любого сайта можно командой:
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
Сервер устанавливает cookie заголовком Set-Cookie, по одному на каждую:
Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400Secure— отправлять только по HTTPS;HttpOnly— недоступна из JavaScript, её не украдёт внедрённый скрипт;SameSite=Lax— не отправлять при запросах с чужих сайтов, кроме обычного перехода по ссылке;Strict— никогда с чужих сайтов;None— всегда (только вместе сSecure). Современные браузеры без явного указания считают cookieLax;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или директива CSPframe-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 нужен, чтобы заголовок отдавался и в ответах с ошибками:
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 задайте доверенные сети явно:
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)
Или оставьте заявку здесь:
// Related