Cache-Control
Cache-Control — главный заголовок управления HTTP-кэшированием. Определяет что, где и как долго кэшировать. Заменил старые заголовки Expires, Pragma. Определён в RFC 9111.
Основные директивы
| Директива | Кто смотрит | Что делает |
|---|---|---|
| public | Все | Можно кэшировать любому (CDN, прокси, браузер) |
| private | Все | Кэшировать может только конечный клиент, не CDN/прокси |
| no-cache | Все | Кэшировать можно, но перед использованием проверить актуальность (revalidate) |
| no-store | Все | Вообще не кэшировать нигде |
| max-age=N | Все | Свежий N секунд |
| s-maxage=N | CDN/прокси | Свежий N секунд (переопределяет max-age для shared caches) |
| must-revalidate | Все | После истечения max-age — обязательно ревалидировать (не отдавать stale) |
| proxy-revalidate | CDN/прокси | То же самое, но только для 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
Кто как кэширует
- Браузер — memory-cache (сессия) + disk-cache (между сессиями). При
Ctrl+F5добавляетCache-Control: no-cacheк запросу, форсируя revalidate. - Service Worker — программируемый кэш в браузере.
- Обратный прокси / CDN — nginx, Varnish, Cloudflare. Уважают
s-maxageиpublic/private. - Приложение — 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:
- Клиент кэшировал ответ с
ETag: "abc"иCache-Control: no-cache. - Следующий запрос:
If-None-Match: "abc". - Сервер: 304 Not Modified → используем кэш. Или 200 OK с новым содержимым.
Vary
Заголовок Vary: Accept-Encoding, Accept-Language говорит: «кэшируй раздельно по этим заголовкам». Один и тот же URL может иметь разные представления (сжатое/несжатое, русская/английская локаль).
Другие HTTP-cache заголовки
- Expires: Wed, 15 Jul 2026 22:00:00 GMT — абсолютная дата, deprecated в пользу max-age.
- Pragma: no-cache — HTTP/1.0 legacy, эквивалент
Cache-Control: no-cache. - Age: 3600 — сколько секунд ответ уже пролежал в кэше (для промежуточных прокси).
Стратегии для типов ресурсов
| Тип | Стратегия |
|---|---|
| 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 без hash | public, max-age=3600 + ETag |
| Пользовательский аватар | private, max-age=86400 |
| Публичное изображение | public, max-age=604800 |