HTTP заголовки Cache-Control Что означают настройки no-cache, no-store и max-age

Обновлено

Практическое объяснение директив Cache-Control — что делают браузеры и CDN с директивами no-cache, no-store, max-age, s-maxage и ETags. Включая ошибки, которые чаще всего встречаются у разработчиков в производении.

Заголовки HTTP Cache-Control: что означают no-cache, no-store и max-age 1
Реклама · УДАЛИТЬ?

Вы, вероятно, писали 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:

  1. Появляется новый запрос к кэшированному ресурсу
  2. Браузер отправляет ETag (через If-None-Match) или последний модифицированный временной отсчёт на сервер
  3. Если содержимое не изменилось → сервер возвращает 304 Not Modified без тела
  4. Если содержимое изменилось → сервер возвращает 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

Хотите убрать рекламу? Откажитесь от рекламы сегодня

Установите наши расширения

Добавьте инструменты ввода-вывода в свой любимый браузер для мгновенного доступа и более быстрого поиска

в Расширение Chrome в Расширение края в Расширение Firefox в Расширение Opera

Табло результатов прибыло!

Табло результатов — это интересный способ следить за вашими играми, все данные хранятся в вашем браузере. Скоро появятся новые функции!

Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?
Реклама · УДАЛИТЬ?

новости с техническими моментами

Примите участие

Помогите нам продолжать предоставлять ценные бесплатные инструменты

Купи мне кофе
Реклама · УДАЛИТЬ?