HTTP заголовки Cache-Control Что означают настройки no-cache, no-store и max-age
Практическое объяснение директив Cache-Control — что делают браузеры и CDN с директивами no-cache, no-store, max-age, s-maxage и ETags. Включая ошибки, которые чаще всего встречаются у разработчиков в производении.
Вы, вероятно, писали Cache-Control: no-cache и предполагали, что браузер полностью пропустит кэш. Это не так. no-cache означает «перепроверить перед предоставлением из кэша». Если вы действительно хотите, чтобы ответ не хранился вообще, то нужно использовать no-store. Эта путаница приводит к ошибкам с устаревшими данными в производственной среде, которые трудно диагностировать, потому что всё выглядит нормально в вкладке «Сеть» при первом загрузке.
Рассмотрим каждую директиву подробно — что делают браузеры с ней, что делают CDN и прокси, и какие ошибки возникают из-за их смешивания.
Полный справочник директив Cache-Control
| Директива | Поведение браузера | Поведение CDN / прокси | Типичное применение |
|---|---|---|---|
max-age=N | Кэшировать на N секунд | Кэшировать на N секунд (если s-maxage отменяет) | Статические ресурсы, ответы API |
s-maxage=N | Игнорируется | Кэшировать на N секунд | ТTL CDN отдельно от браузера |
no-cache | Кэшировать, но перепроверять каждый запрос | Кэшировать, но перепроверять каждый запрос | Часто изменяющийся контент с ETags |
no-store | Не хранить вовсе | Не хранить вовсе | Ответы авторизации, чувствительные данные пользователей |
must-revalidate | Нет устаревшего обслуживания — перепроверять или отказаться | Нет устаревшего обслуживания — перепроверять или отказаться | Ответы API, где устаревшие данные приводят к сбоям |
proxy-revalidate | Игнорируется | Нет устаревшего обслуживания в общих кэшах | CDN-специфический must-revalidate |
private | Браузер может кэшировать | Не кэшировать | Страницы, связанные с пользователем |
public | Любые кэши могут хранить | Может кэшировать | Общие статические ресурсы |
immutable | Никогда не перепроверять в течение max-age | Варьируется в зависимости от CDN | Хэшированные / версионированные ассеты |
stale-while-revalidate=N | Предоставлять устаревшие данные на N секунд, пока загружаются свежие | Варьируется в зависимости от CDN | Скорость без жёсткого устаревания |
max-age и s-maxage: TTL браузера против TTL CDN
max-age=N означает, сколько секунд ответ остаётся свежим. После N секунд кэшированный ответ становится устаревшим и должен быть перепроверен перед использованием.
s-maxage=N — это только для CDN. Браузеры полностью игнорируют его. Если вы хотите, чтобы CDN кэшировал ответ на один час, а браузер — только на 5 минут:
Cache-Control: max-age=300, s-maxage=3600
Браузер кэширует на 5 минут. CloudFront, Fastly, Nginx — они используют 3600 секунд. Частая ошибка: установка max-age=0 предполагая, что это отключает кэширование. Это не так. max-age=0 означает, что ответ немедленно становится устаревшим — браузер всё равно кэширует его и перепроверяет, возможно, получая 304. Если вы никогда не хотите, чтобы он кэшировался, используйте no-store.
no-cache: Не то, что вы думаете
Cache-Control: no-cache означает «не предоставлять из кэша без предварительной перепроверки с сервера». Это не значит «не использовать кэш».
Последовательность при наличии кэшированного ответа с no-cache:
- Появляется новый запрос к кэшированному ресурсу
- Браузер отправляет ETag (через
If-None-Match) или последний модифицированный временной отсчёт на сервер - Если содержимое не изменилось → сервер возвращает
304 Not Modifiedбез тела - Если содержимое изменилось → сервер возвращает
200 OKс новым ответом
Преимущество перед no-store: вы всё ещё получаете экономию пропускной способности за счёт ответов 304. Если ваш контент редко меняется, но должен быть свежим при изменении, no-cache в сочетании с ETag — правильный выбор.
no-store: Реальное «Не кэшировать это»
Cache-Control: no-store означает, что ответ не должен храниться никуда — ни в кэше браузера, ни в CDN, ни в промежуточном прокси. Никаких копий, окончательно.
Используйте это для:
- Ответы авторизации (токены входа, данные сессии)
- Чувствительные персональные данные
- Одноразовый контент (подтверждения оплаты, страницы OTP)
Одна тонкость: no-store не предотвращает появления страницы в кэше браузера (bfcache). Браузеры сохраняют в памяти снимок для повышения производительности навигации, который отделён от HTTP-кэша. Если вам нужно решать проблемы с кнопкой «назад» после выхода из сессии, подключитесь к событию pageshow и проверьте event.persisted.
must-revalidate: Нет устаревшего грейс-периода
Спецификации HTTP-кэширования позволяют кэшам предоставлять устаревшие ответы, когда исходный сервер недоступен — это функция устойчивости, которую большинство разработчиков не знают. must-revalidate удаляет эту свободу: как только кэшированный ответ становится устаревшим, кэш должен перепроверить или вернуть 504. Никакого устаревшего обслуживания в любых обстоятельствах.
# Without must-revalidate: CDN may serve stale if origin is slow or down
Cache-Control: max-age=3600
# With must-revalidate: stale = error, not a fallback
Cache-Control: max-age=3600, must-revalidate
Используйте это для ответов API, где предоставление устаревших данных нарушает функциональность — количество товаров, цены, состояние авторизации — а не просто выглядит неправильно.
private против public: Ошибка CDN, которая утечка пользовательских данных
private означает, что ответ предназначен для конкретного пользователя. Браузеры могут кэшировать его; общие кэши (CDN, обратные прокси) не должны.
public является явным разрешением для любого кэша — включая общие — хранить ответ. Некоторые кэши кэшируют только ответы на авторизованные запросы, если вы явно помечаете их public.
Ошибочная ситуация в реальности: разработчик копирует Cache-Control: public, max-age=3600 из статического ресурса на страницу, содержащую пользовательские данные. CDN кэширует ответ. Пользователь B делает такой же запрос и получает страницу пользователя A из кэша. Это не теоретическая ситуация — GitHub имел подобный пример в 2018 году. Явно помечайте авторизованные или пользовательские ответы private даже если вы думаете, что ваш CDN «понимает», что не должен кэшировать их.
ETags и условные запросы
ETags — это то, как сервер говорит: «здесь фингерпринт ответа». Браузер сохраняет ETag вместе с кэшированным ответом и отправляет его на следующий запрос через If-None-Match. Если содержимое не изменилось, сервер возвращает 304 Not Modified без тела — то же самое обеспечение свежести, что и no-cache, но с меньшим расходом пропускной способности.
The no-cache + Поток ETag:
→ GET /api/config HTTP/1.1
← HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "abc123"
[full response body]
→ GET /api/config HTTP/1.1
If-None-Match: "abc123"
← HTTP/1.1 304 Not Modified
[no body — browser uses its cached copy]
Две формы ETag:
- Сильные ETags (
"abc123") — идентичны по байтам. Обязательны для поддержки диапазонных запросов в CDN. - Слабые ETags (
W/"abc123") — семантически эквивалентны, но не обязательно идентичны по байтам. Подходят для перепроверки в браузере, но не для диапазонных запросов.
Nginx генерирует ETags автоматически на основе времени модификации файла и его размера. Express по умолчанию не добавляет их — используйте app.set('etag', 'strong') или etag миддлвар для явного добавления.
Last-Modified и If-Modified-Since
То же самое понятие, что и ETags, но более грубое — основанное на временных метках вместо хэшей содержимого. Сервер включает Last-Modified; браузер отправляет If-Modified-Since на последующие запросы.
Проблема: если вы перезапускаете и время модификации файлов обновляется, хотя содержимое не изменилось, кэшируются необоснованно. Хэш-основанный ETag не будет иметь этой проблемы. Используйте ETags, где возможно, и используйте Last-Modified как резерв для серверов, не поддерживающих ETags.
Vary: Заголовок, который без осознания увеличивает ваш кэш
The Vary заголовок говорит кэшам, что ответ может отличаться в зависимости от других заголовков запроса. Каждая уникальная комбинация этих заголовков получает свой собственный кэш.
Vary: Accept-Encoding
Это говорит кэшам о том, чтобы хранить отдельные ответы для gzip, brotli и идентичного кодирования. Правильно и распространено. Опасный случай: Vary: Cookie. У каждого пользователя есть уникальный куки, поэтому каждый пользователь получает свой собственный кэш — фактически отключая общий кэш. Многие фреймворки добавляют Vary: Cookie без осознания. Если ваша ставка кэша неожиданно низка, несмотря на большие значения max-age проверьте заголовки ответа на наличие Vary: Cookie взятые из среды сессии.
Vary: * означает «не кэшировать вообще» на практике — каждый запрос рассматривается как уникальный. Это эквивалентно no-store для CDN.
Кэширование с помощью параметров запроса
Когда нужно принудительно загружать свежие версии ассетов, прикрепление параметра запроса — стандартный подход — строка запроса является частью URL, поэтому она рассматривается как новый ресурс как для браузера, так и для CDN:
/app.js?v=2.1.4
/styles.css?hash=a1b2c3d4e5f6
Если вы динамически создаёте параметры кэширования из хэшей содержимого или версий, которые могут содержать специальные символы, убедитесь, что они правильно кодируются перед прикреплением. Утилита URL Encoder/Decoder быстро решает эту проблему при тестировании или при создании URL вручную.
Три ошибки, которые чаще всего мучают разработчиков
1. Использование no-cache вместо no-store. Если вы обрабатываете ответы авторизации, точки выхода из сессии или любые данные с PII, вам нужно использовать no-store. no-cache оставляет данные в кэше браузера (только помеченные как устаревшие); no-store удаляет следы полностью. Разница важна при использовании пользователем общего устройства.
2. Не настройка s-maxage для контроля CDN. Скачать как .yml s-maxage, ваш CDN использует max-age. Если max-age коротко для свежести браузера (например, 60 секунд), ваш CDN кэширует на 60 секунд — что, вероятно, не то, что вы хотите. Явно разделяйте эти TTL.
3. Использование public на концах, возвращающих данные пользователей. Это одна из катастрофических ошибок, а не просто производительность. Любой ответ, который персонализирован или авторизован, должен быть private. По умолчанию используйте private и переходите к public только для действительно общих ресурсов.
Сборка всей системы
Ментальная модель: no-cache — это о защите свежести — ответ находится в кэше, но требует подтверждения от сервера перед использованием. no-store — это о том, чтобы не оставлять следов. max-age — это TTL вашего браузера. s-maxage — это TTL вашего CDN. ETags делают перепроверку дешевой.
Правильно понимайте различие на любом конечном пункте, который касается пользовательских данных. Ошибка — копирование заголовка кэширования из статического ресурса на конечный пункт авторизации — превращается в утечку данных между пользователями, когда ваш CDN начинает кэшировать персонализированные ответы. private/public HTTP заголовки Cache-Control: что означают no-cache, no-store и max-age на самом деле 2
Установите наши расширения
Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска
恵 Табло результатов прибыло!
Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!
Подписаться на новости
все Новые поступления
всеОбновлять: Наш последний инструмент было добавлено 18 Июня, 2026
