Лаборатория кодера

Справочник · Веб и HTTP

Заголовки HTTP: кеш, CORS, cookie, безопасность

Основные заголовки запросов и ответов с примерами: как работает кеш и ETag, что проверяет CORS, какие атрибуты нужны cookie и какие заголовки безопасности стоит отдавать любому сайту.

Обновлено · 8 мин чтения

Заголовки — пары «имя: значение» перед телом запроса и ответа. Имена не зависят от регистра: Content-Type и content-type одно и то же, а в HTTP/2 и HTTP/3 они вообще передаются строчными буквами. Ниже те, с которыми приходится иметь дело чаще всего.

Запрос: кто, что и в каком виде хочет

ЗаголовокПримерЗачем
HostHost: api.local:8080какой сайт на этом сервере нужен; обязателен в HTTP/1.1
User-AgentUser-Agent: curl/8.5.0программа клиента
AcceptAccept: application/jsonкакой формат ответа устроит
Accept-LanguageAccept-Language: ru,en;q=0.8предпочитаемые языки с весами
Accept-EncodingAccept-Encoding: gzip, br, zstdкакие сжатия клиент понимает
AuthorizationAuthorization: Bearer eyJhbGci...учётные данные
Content-TypeContent-Type: application/jsonформат тела запроса
Content-LengthContent-Length: 348размер тела в байтах
CookieCookie: sid=a1b2; theme=darkcookie, ранее выданные сервером
Refererполный адрес страницыоткуда пришёл пользователь (да, с опечаткой, так в стандарте)
Originсхема, домен и порт страницыисточник запроса для проверок CORS
If-None-MatchIf-None-Match: "33a64df5"условный запрос по ETag
If-Modified-SinceIf-Modified-Since: Tue, 14 Oct 2026 08:00:00 GMTусловный запрос по дате
RangeRange: bytes=0-1023нужна часть файла

Числа q=0.8 в Accept-* — веса от 0 до 1, по умолчанию 1. Accept: text/html, application/json;q=0.9, */*;q=0.1 означает: лучше HTML, потом JSON, в крайнем случае что угодно.

Authorization

СхемаВидКомментарий
BasicBasic dXNlcjpwYXNzлогин и пароль через двоеточие в Base64; это не шифрование, только кодирование
BearerBearer <токен>токен доступа, часто JWT

Строку Basic можно раскодировать любым декодером Base64, поэтому такая схема допустима только поверх HTTPS. Содержимое JWT-токена показывает разбор JWT.

Ответ: что и как отдано

ЗаголовокПримерЗачем
Content-TypeContent-Type: text/html; charset=utf-8тип содержимого и кодировка
Content-LengthContent-Length: 5120размер тела
Content-EncodingContent-Encoding: brчем сжато тело
Content-DispositionContent-Disposition: attachment; filename="report.csv"скачать файлом, а не показать
LocationLocation: /orders/1042куда перенаправить или где новый ресурс
Retry-AfterRetry-After: 120через сколько секунд повторить (429, 503)
AllowAllow: GET, POSTразрешённые методы (405)
WWW-AuthenticateWWW-Authenticate: Bearerкак аутентифицироваться (401)
ServerServer: nginxпрограмма сервера; версию лучше не показывать
DateDate: Fri, 17 Oct 2026 09:30:00 GMTвремя формирования ответа

Частые значения Content-Type:

ТипДля чего
text/html; charset=utf-8страницы
application/jsonAPI; для JSON кодировка всегда UTF-8, charset не нужен
application/x-www-form-urlencodedобычная отправка формы: a=1&b=%D0%BF
multipart/form-data; boundary=...форма с файлами
text/csv; charset=utf-8таблицы
application/octet-streamпроизвольные байты, «просто скачать»

Кодировки и почему в charset должно стоять utf-8, разобраны в статье про UTF-8, а процентное кодирование из формы можно проверить в URL-кодировщике.

Кеширование

Главный заголовок — Cache-Control. Его директивы комбинируются через запятую.

ДирективаСмысл
max-age=3600ответ свежий 3600 секунд, всё это время можно не спрашивать сервер
no-cacheхранить можно, но перед каждым использованием проверять у сервера
no-storeне хранить вообще: для личных данных, банковских страниц
privateтолько в браузере пользователя, не в общих кешах
publicможно хранить в общих кешах, даже если запрос был с авторизацией
immutableсодержимое по этому адресу никогда не изменится
must-revalidateпосле истечения срока обязательно проверить, нельзя отдавать устаревшее
s-maxage=600срок для общих кешей, например CDN, отдельно от браузера
stale-while-revalidate=60ещё 60 секунд можно отдавать старое, обновляя в фоне

Удобная схема для сайта:

text
# HTML: всегда спрашивать, не изменилось ли
Cache-Control: no-cache

# Стили и скрипты с хешем в имени: app.3f9a2c.js
Cache-Control: public, max-age=31536000, immutable

Проверка свежести: ETag и Last-Modified

Сервер отдаёт «версию» ресурса:

http
HTTP/1.1 200 OK
ETag: "33a64df5"
Last-Modified: Tue, 14 Oct 2026 08:00:00 GMT
Cache-Control: no-cache

В следующий раз браузер спрашивает условно:

http
GET /app.css HTTP/1.1
If-None-Match: "33a64df5"

Если ресурс не поменялся, сервер отвечает 304 Not Modified без тела, и трафик экономится. ETag обычно считают хешем содержимого; приставка W/ означает «слабый» ETag, который допускает несущественные отличия.

Vary

Vary: Accept-Encoding говорит кешам, что ответ зависит от этого заголовка запроса и для каждого его значения нужна своя копия. Без него кеш может отдать сжатый ответ клиенту, который не понимает сжатие. Vary: Cookie или Vary: Authorization фактически делают ответ личным для каждого пользователя.

CORS

Браузер не даёт скрипту со страницы одного источника читать ответы другого источника. Источник — это схема, домен и порт вместе: app.local:3000 и api.local:8080 разные. CORS — способ серверу сказать браузеру, что такое чтение разрешено.

Простой запрос (GET или POST с обычными заголовками) браузер отправляет сразу, а потом смотрит на ответ:

text
Access-Control-Allow-Origin: <точное значение Origin из запроса>

Сервер повторяет источник из заголовка Origin запроса целиком, вместе со схемой и портом, если этот источник есть в его списке разрешённых. Значение * разрешает любой источник, но не работает вместе с cookie и авторизацией.

Если запрос «непростой» — метод PUT или DELETE, заголовок Authorization, Content-Type: application/json — браузер сначала отправляет предварительный запрос OPTIONS:

Заголовок предварительного ответаЧто разрешает
Access-Control-Allow-Originкакой источник
Access-Control-Allow-Methods: GET, POST, PUT, DELETEкакие методы
Access-Control-Allow-Headers: Content-Type, Authorizationкакие заголовки запроса
Access-Control-Allow-Credentials: trueможно отправлять cookie
Access-Control-Max-Age: 600сколько секунд помнить этот ответ
Access-Control-Expose-Headers: X-Total-Countкакие заголовки ответа скрипт может прочитать

Cookie

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

http
Set-Cookie: sid=a1b2c3; Path=/; Max-Age=2592000; HttpOnly; Secure; SameSite=Lax
АтрибутЧто делает
Max-Age=N / Expires=датасрок жизни; без них cookie живёт до закрытия браузера
Path=/для каких путей отправлять
Domain=site.localотправлять и поддоменам; без атрибута только точному домену
Secureтолько по HTTPS
HttpOnlyнедоступна из JavaScript, защищает сессию от кражи через XSS
SameSite=Laxне отправлять при запросах с чужих сайтов, кроме обычного перехода по ссылке
SameSite=Strictне отправлять ни при каких переходах с чужих сайтов
SameSite=Noneотправлять всегда; требует Secure

Для сессионной cookie разумный минимум: HttpOnly; Secure; SameSite=Lax. Префикс имени __Host- дополнительно требует Secure, Path=/ и отсутствие Domain, и браузер не примет такую cookie, если условие не выполнено.

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

Эти заголовки стоит отдавать на любом сайте. Их проще всего задать один раз в настройках веб-сервера.

ЗаголовокЗначениеЧто даёт
Strict-Transport-Securitymax-age=31536000; includeSubDomainsбраузер год не будет открывать сайт по HTTP
Content-Security-Policydefault-src 'self'разрешает загружать скрипты, стили и картинки только со своего сайта
X-Content-Type-Optionsnosniffбраузер не угадывает тип файла, а верит Content-Type
Referrer-Policystrict-origin-when-cross-originна чужие сайты уходит только домен, без пути и параметров
X-Frame-OptionsDENYстраницу нельзя встроить во фрейм на другом сайте
Permissions-Policycamera=(), microphone=(), geolocation=()отключить доступ к устройствам для страницы и фреймов

Пример строгой политики CSP для сайта без внешних ресурсов:

text
Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self';
  script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

frame-ancestors 'none' в CSP — современная замена X-Frame-Options: DENY. Политику удобно сначала включить в режиме наблюдения через заголовок Content-Security-Policy-Report-Only: браузер будет писать нарушения в консоль, ничего не ломая.

Как посмотреть заголовки

bash
curl -I localhost:8080/                 # только заголовки ответа, методом HEAD
curl -sv localhost:8080/ -o /dev/null   # заголовки запроса и ответа
curl -s -D - localhost:8080/ -o /dev/null

В браузере заголовки видны во вкладке «Сеть»: выберите запрос, раздел «Заголовки». Подробнее о запросах из терминала в шпаргалке по curl.