Cache-Control

Cache-Control — главный заголовок управления HTTP-кэшированием. Определяет что, где и как долго кэшировать. Заменил старые заголовки Expires, Pragma. Определён в RFC 9111.

Основные директивы

ДирективаКто смотритЧто делает
publicВсеМожно кэшировать любому (CDN, прокси, браузер)
privateВсеКэшировать может только конечный клиент, не CDN/прокси
no-cacheВсеКэшировать можно, но перед использованием проверить актуальность (revalidate)
no-storeВсеВообще не кэшировать нигде
max-age=NВсеСвежий N секунд
s-maxage=NCDN/проксиСвежий N секунд (переопределяет max-age для shared caches)
must-revalidateВсеПосле истечения max-age — обязательно ревалидировать (не отдавать stale)
proxy-revalidateCDN/проксиТо же самое, но только для shared caches
immutableБраузерРесурс не поменяется никогда — не ревалидируй даже при F5
stale-while-revalidate=NВсеЕщё N секунд после истечения можно отдать stale, параллельно обновляя
stale-if-error=NВсеЕсли origin упал — отдай stale ещё N секунд
no-transformВсеНе трогать содержимое (не пере-сжимать, не менять)

Типичные наборы

# Не кэшировать вообще (HTML главной)
Cache-Control: no-store

# Всегда проверять актуальность (dynamic API)
Cache-Control: no-cache

# Приватный кэш браузера на 5 минут
Cache-Control: private, max-age=300

# CDN + браузер на час
Cache-Control: public, max-age=3600

# Разное время для CDN и браузера
Cache-Control: public, max-age=60, s-maxage=3600

# Immutable статика (JS/CSS с hash в имени)
Cache-Control: public, max-age=31536000, immutable

# Отдаём stale ещё 24 часа если origin упал
Cache-Control: public, max-age=3600, stale-if-error=86400

Кто как кэширует

  1. Браузер — memory-cache (сессия) + disk-cache (между сессиями). При Ctrl+F5 добавляет Cache-Control: no-cache к запросу, форсируя revalidate.
  2. Service Worker — программируемый кэш в браузере.
  3. Обратный прокси / CDN — nginx, Varnish, Cloudflare. Уважают s-maxage и public/private.
  4. Приложение — Redis, Memcached (это уже не HTTP, но семантика похожая).

Свежий, stale, revalidate

t=0: получил ответ с max-age=60, stale-while-revalidate=30
[0..60]     ─── fresh ───    отдаём из кэша
[60..90]    ─── stale ───    отдаём из кэша + параллельно обновляем в фоне
[90..∞]     ─── expired ──   идём в origin, ждём ответа

Взаимодействие с ETag / Last-Modified

Cache-Control: no-cache не значит «не кэшировать». Он значит: «кэшируй, но проверяй». Комбинируется с ETag / Last-Modified:

  1. Клиент кэшировал ответ с ETag: "abc" и Cache-Control: no-cache.
  2. Следующий запрос: If-None-Match: "abc".
  3. Сервер: 304 Not Modified → используем кэш. Или 200 OK с новым содержимым.

Vary

Заголовок Vary: Accept-Encoding, Accept-Language говорит: «кэшируй раздельно по этим заголовкам». Один и тот же URL может иметь разные представления (сжатое/несжатое, русская/английская локаль).

Другие HTTP-cache заголовки

Стратегии для типов ресурсов

ТипСтратегия
HTML (главная)no-cache или короткий max-age. Хочешь быстро обновить.
API (dynamic)no-store или no-cache
API (данные редко меняются)public, max-age=60
JS/CSS с hash в имени (app.a1b2c3.js)public, max-age=31536000, immutable
JS/CSS без hashpublic, max-age=3600 + ETag
Пользовательский аватарprivate, max-age=86400
Публичное изображениеpublic, max-age=604800

См. также