Справочник · Веб и HTTP
Заголовки HTTP: кеш, CORS, cookie, безопасность
Основные заголовки запросов и ответов с примерами: как работает кеш и ETag, что проверяет CORS, какие атрибуты нужны cookie и какие заголовки безопасности стоит отдавать любому сайту.
Заголовки — пары «имя: значение» перед телом запроса и ответа. Имена не зависят от регистра: Content-Type и content-type одно и то же, а в HTTP/2 и HTTP/3 они вообще передаются строчными буквами. Ниже те, с которыми приходится иметь дело чаще всего.
Запрос: кто, что и в каком виде хочет
| Заголовок | Пример | Зачем |
|---|---|---|
Host | Host: api.local:8080 | какой сайт на этом сервере нужен; обязателен в HTTP/1.1 |
User-Agent | User-Agent: curl/8.5.0 | программа клиента |
Accept | Accept: application/json | какой формат ответа устроит |
Accept-Language | Accept-Language: ru,en;q=0.8 | предпочитаемые языки с весами |
Accept-Encoding | Accept-Encoding: gzip, br, zstd | какие сжатия клиент понимает |
Authorization | Authorization: Bearer eyJhbGci... | учётные данные |
Content-Type | Content-Type: application/json | формат тела запроса |
Content-Length | Content-Length: 348 | размер тела в байтах |
Cookie | Cookie: sid=a1b2; theme=dark | cookie, ранее выданные сервером |
Referer | полный адрес страницы | откуда пришёл пользователь (да, с опечаткой, так в стандарте) |
Origin | схема, домен и порт страницы | источник запроса для проверок CORS |
If-None-Match | If-None-Match: "33a64df5" | условный запрос по ETag |
If-Modified-Since | If-Modified-Since: Tue, 14 Oct 2026 08:00:00 GMT | условный запрос по дате |
Range | Range: bytes=0-1023 | нужна часть файла |
Числа q=0.8 в Accept-* — веса от 0 до 1, по умолчанию 1. Accept: text/html, application/json;q=0.9, */*;q=0.1 означает: лучше HTML, потом JSON, в крайнем случае что угодно.
Authorization
| Схема | Вид | Комментарий |
|---|---|---|
| Basic | Basic dXNlcjpwYXNz | логин и пароль через двоеточие в Base64; это не шифрование, только кодирование |
| Bearer | Bearer <токен> | токен доступа, часто JWT |
Строку Basic можно раскодировать любым декодером Base64, поэтому такая схема допустима только поверх HTTPS. Содержимое JWT-токена показывает разбор JWT.
Ответ: что и как отдано
| Заголовок | Пример | Зачем |
|---|---|---|
Content-Type | Content-Type: text/html; charset=utf-8 | тип содержимого и кодировка |
Content-Length | Content-Length: 5120 | размер тела |
Content-Encoding | Content-Encoding: br | чем сжато тело |
Content-Disposition | Content-Disposition: attachment; filename="report.csv" | скачать файлом, а не показать |
Location | Location: /orders/1042 | куда перенаправить или где новый ресурс |
Retry-After | Retry-After: 120 | через сколько секунд повторить (429, 503) |
Allow | Allow: GET, POST | разрешённые методы (405) |
WWW-Authenticate | WWW-Authenticate: Bearer | как аутентифицироваться (401) |
Server | Server: nginx | программа сервера; версию лучше не показывать |
Date | Date: Fri, 17 Oct 2026 09:30:00 GMT | время формирования ответа |
Частые значения Content-Type:
| Тип | Для чего |
|---|---|
text/html; charset=utf-8 | страницы |
application/json | API; для 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 секунд можно отдавать старое, обновляя в фоне |
Удобная схема для сайта:
# HTML: всегда спрашивать, не изменилось ли
Cache-Control: no-cache
# Стили и скрипты с хешем в имени: app.3f9a2c.js
Cache-Control: public, max-age=31536000, immutableПроверка свежести: ETag и Last-Modified
Сервер отдаёт «версию» ресурса:
HTTP/1.1 200 OK
ETag: "33a64df5"
Last-Modified: Tue, 14 Oct 2026 08:00:00 GMT
Cache-Control: no-cacheВ следующий раз браузер спрашивает условно:
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 с обычными заголовками) браузер отправляет сразу, а потом смотрит на ответ:
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, по одному заголовку на каждую:
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-Security | max-age=31536000; includeSubDomains | браузер год не будет открывать сайт по HTTP |
Content-Security-Policy | default-src 'self' | разрешает загружать скрипты, стили и картинки только со своего сайта |
X-Content-Type-Options | nosniff | браузер не угадывает тип файла, а верит Content-Type |
Referrer-Policy | strict-origin-when-cross-origin | на чужие сайты уходит только домен, без пути и параметров |
X-Frame-Options | DENY | страницу нельзя встроить во фрейм на другом сайте |
Permissions-Policy | camera=(), microphone=(), geolocation=() | отключить доступ к устройствам для страницы и фреймов |
Пример строгой политики CSP для сайта без внешних ресурсов:
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: браузер будет писать нарушения в консоль, ничего не ломая.
Как посмотреть заголовки
curl -I localhost:8080/ # только заголовки ответа, методом HEAD
curl -sv localhost:8080/ -o /dev/null # заголовки запроса и ответа
curl -s -D - localhost:8080/ -o /dev/nullВ браузере заголовки видны во вкладке «Сеть»: выберите запрос, раздел «Заголовки». Подробнее о запросах из терминала в шпаргалке по curl.