Cache-Control
Применение
Источники, промежуточные узлы и клиенты опираются на Cache-Control, чтобы согласовать, когда сохранённый ответ остаётся пригодным к использованию, а когда нужна свежая копия. Один ответ часто сочетает несколько директив, выражая полную политику кэширования: как долго хранить, кому разрешено хранить и что делать после истечения свежести.
В таблице ниже показано, к чему применяется каждая директива: к запросам, ответам или к тому и другому.
Несколько директив разделяются запятыми в одном значении заголовка либо распределяются по нескольким заголовкам Cache-Control. При появлении конфликтующих директив применяется наиболее строгая комбинация.
Примечание
Нераспознанные директивы игнорируются кэшами. Это позволяет вводить новые директивы, не ломая старые реализации.
Примечание
Когда заголовок Cache-Control отсутствует, кэши применяют эвристическую свежесть. Распространённая эвристика — 10 % от времени, прошедшего с Last-Modified. Это означает, что ответы без Cache-Control кэшируются браузерами и CDN. Явная установка Cache-Control предотвращает неожиданное поведение кэширования.
Примечание
Cache-Control определяет, когда выполнять повторную валидацию (время жизни свежести). ETag определяет, как её выполнять (идентичность содержимого). Они работают вместе: Cache-Control решает, нужна ли повторная валидация, а ETag — изменилось ли содержимое. Заголовок Pragma — это эквивалент Cache-Control: no-cache из HTTP/1.0, фактически считающийся устаревшим.
max-age
Директива max-age указывает кэшам возвращать сохранённый ответ только в том случае, если его возраст не превышает заданного числа секунд. Некорректные значения, например отрицательные числа, трактуются как 0.
Cache-Control: max-age=<seconds>
Установка max-age=0 принудительно запускает сквозную повторную валидацию. Этот приём возник в реализациях HTTP/1.0, не поддерживавших no-cache.
max-stale
Директива max-stale сообщает о готовности принять ответ, у которого Age превысил время жизни свежести не более чем на указанное число секунд. Окно допуска начинается после истечения max-age.
Cache-Control: max-stale=<seconds>
Эта директива полезна, когда сервер-источник временно недоступен, а слегка устаревший ответ допустим.
min-fresh
Директива min-fresh запрашивает сохранённый ответ, у которого оставшееся время жизни свежести составляет не менее указанного числа секунд. Кэш возвращает сохранённую копию, только если ответ останется свежим в течение этого дополнительного периода.
Cache-Control: min-fresh=<seconds>
no-cache
Запрос с директивой no-cache требует, чтобы кэш перед выдачей копии проверил сохранённый ответ у источника. Это принудительно запускает повторную валидацию, не отбрасывая сохранённую запись.
Cache-Control: no-cache
Примечание
Чтобы полностью запретить кэшу сохранять ответ, используйте no-store.
no-store
Директива no-store просит кэши не сохранять ни запрос, ни соответствующий ответ.
Cache-Control: no-store
no-transform
Директива no-transform запрещает промежуточным узлам изменять тело ответа, например повторно сжимать изображения или преобразовывать форматы медиа. Ограничение действует независимо от того, является ли промежуточный узел кэширующим прокси или шлюзом-пересыльщиком.
Cache-Control: no-transform
only-if-cached
Директива only-if-cached указывает кэшу вернуть сохранённый ответ, не обращаясь к источнику. Если подходящего сохранённого ответа нет, кэш возвращает статус 504.
Cache-Control: only-if-cached
max-age
Директива max-age объявляет число секунд, в течение которых ответ остаётся свежим после генерации. Отсчёт начинается с момента создания ответа источником, поэтому время передачи и время, проведённое в промежуточных кэшах, вычитаются из этого бюджета.
Cache-Control: max-age=<seconds>
Ответ с max-age=3600 остаётся свежим один час. По истечении часа кэши считают ответ устаревшим и либо выполняют повторную валидацию, либо получают новую копию — в зависимости от других присутствующих директив.
s-maxage
Директива s-maxage (shared max-age) переопределяет max-age для разделяемых кэшей, таких как CDN и прокси-серверы. Частные браузерные кэши игнорируют s-maxage и возвращаются к max-age.
Cache-Control: s-maxage=<seconds>
no-cache
Директива no-cache в ответе требует, чтобы кэши выполняли повторную валидацию сохранённого ответа у источника перед каждым использованием. Кэш по-прежнему сохраняет ответ, что позволяет делать условные запросы с ETag или Last-Modified.
Cache-Control: no-cache
Когда no-cache включает список имён полей, повторной валидации требуют только эти конкретные заголовки. Остальная часть сохранённого ответа выдаётся без обращения к источнику.
Cache-Control: no-cache="Set-Cookie"
Примечание
Чтобы полностью запретить сохранение, используйте no-store.
no-store
Директива no-store запрещает любому кэшу сохранять ответ. Каждый последующий запрос уходит к источнику.
Cache-Control: no-store
Примечание
no-cache разрешает сохранение, но требует повторной валидации перед каждым использованием. Кэш сверяется с источником через If-None-Match или If-Modified-Since и получает 304, если содержимое не изменилось. no-store полностью запрещает сохранение, поэтому никакая копия не хранится и каждый запрос получает полный ответ. Распространённая ошибка — использовать no-cache там, где для конфиденциальных данных вроде банковских или медицинских записей подразумевался no-store. no-cache эффективен для часто обновляемого содержимого. no-store предназначен для содержимого, которое никогда не должно сохраняться на диск.
no-transform
Директива no-transform в ответе запрещает промежуточным узлам изменять тело ответа перед пересылкой независимо от того, кэширует ли узел содержимое.
Cache-Control: no-transform
must-revalidate
Директива must-revalidate разрешает кэшам выдавать ответ, пока он свеж. Как только ответ устарел, кэш обращается к источнику для повторной валидации, прежде чем выдать его снова. Если источник недоступен, кэш возвращает 504 вместо выдачи устаревшего содержимого.
Cache-Control: must-revalidate
proxy-revalidate
Директива proxy-revalidate работает так же, как must-revalidate, за исключением того, что требование применяется только к разделяемым кэшам. Частные браузерные кэши не затрагиваются.
Cache-Control: proxy-revalidate
must-understand
Директива must-understand предписывает кэшу сохранять ответ только тогда, когда кэш распознаёт код состояния и понимает связанные с ним требования к кэшированию.
Cache-Control: must-understand, no-store
Сочетание must-understand с no-store даёт запасной вариант: кэши без поддержки must-understand игнорируют неизвестную директиву и вместо этого соблюдают no-store.
private
Директива private ограничивает хранение частными кэшами, как правило браузером конечного пользователя. Разделяемые кэши, такие как CDN и прокси-серверы, отбрасывают ответ.
Cache-Control: private
public
Директива public помечает ответ как пригодный для хранения в разделяемых кэшах. Ответы с заголовком Authorization не сохраняются разделяемыми кэшами, если не присутствует директива public.
Cache-Control: public
Примечание
Директива public не нужна, когда уже присутствует must-revalidate или s-maxage.
immutable
Директива immutable гарантирует, что тело ответа не изменится в течение времени жизни свежести. Кэши пропускают условные запросы для неизменяемых ресурсов, устраняя обращения на повторную валидацию для таких ресурсов, как версионированные JavaScript-бандлы или изображения с отпечатком в имени.
Cache-Control: public, max-age=31536000, immutable
stale-while-revalidate
Директива stale-while-revalidate продлевает окно пригодности устаревшего ответа. После истечения свежести кэши выдают устаревшую копию, параллельно выполняя повторную валидацию в фоне. Параметр задаёт, сколько дополнительных секунд устаревший ответ остаётся приемлемым.
Cache-Control: max-age=600, stale-while-revalidate=30
Как только фоновая повторная валидация завершается, кэш заменяет устаревшую запись свежим ответом. Этот приём скрывает задержку повторной валидации от конечных пользователей.
stale-if-error
Директива stale-if-error позволяет кэшам выдавать устаревший ответ, когда источник возвращает ошибочный статус (500, 502, 503 или 504) либо недоступен. Параметр задаёт число секунд сверх времени жизни свежести, в течение которых устаревший ответ остаётся пригодным в качестве запасного варианта.
Cache-Control: max-age=600, stale-if-error=86400
Пример
Статический ресурс, выдаваемый с длительным временем жизни свежести и флагом immutable. Браузер и любой разделяемый кэш хранят этот ответ год без повторной валидации.
Cache-Control: public, max-age=31536000, immutable
Ответ API, предназначенный только для запросившего его браузера. Кэш хранит ответ пять минут и выполняет повторную валидацию перед повторным использованием, когда ответ устарел.
Cache-Control: private, max-age=300, must-revalidate
Политика, нацеленная на CDN: разделяемому кэшу даётся десятиминутное окно, а браузерному — одна минута. Устаревшие ответы выдаются до 30 секунд, пока CDN выполняет повторную валидацию в фоне.
Cache-Control: s-maxage=600, max-age=60, stale-while-revalidate=30
Googlebot и max-age
Инфраструктура сканирования Google реализует эвристическое HTTP-кэширование. Директива max-age помогает Googlebot определить частоту повторного сканирования. Страница с большим max-age запрашивается заново реже, тогда как no-cache сигнализирует о частых изменениях содержимого и оправдывает более частое сканирование. Для некоторых кодов состояния ответы без каких-либо заголовков кэширования по умолчанию кэшируются эвристически, поэтому источникам, желающим полностью исключить кэширование, нужно явно отправлять no-store. Bingbot отправляет в запросах Cache-Control: no-cache и для регулирования темпа сканирования опирается на crawl-delay в robots.txt и IndexNow.
Примечание
За помощью по SEO и кэшированию обращайтесь к бывшим SEO-консультантам Google Search Brothers.
Диагностика
Неожиданное поведение кэширования возникает из-за неверного понимания директив, переопределений на стороне CDN или конфликтующих заголовков.
Устаревшее содержимое выдаётся несмотря на директиву no-cache. Директива no-cache не запрещает сохранение. Кэши сохраняют ответ и выполняют повторную валидацию при каждом использовании. Чтобы полностью запретить сохранение, используйте no-store. Когда речь идёт о конфиденциальных данных, комбинируйте обе: Cache-Control: no-cache, no-store.
CDN игнорирует директивы Cache-Control. Многие CDN переопределяют Cache-Control источника собственными политиками. Cloudflare по умолчанию соблюдает заголовки источника, но переопределяет их при активном Page Rule или Cache Rule. AWS CloudFront использует заголовок источника, если не подключена политика кэширования с собственными TTL. Прежде чем отлаживать конфигурацию источника, проверьте правила переопределения в панели управления CDN.
Устаревшее содержимое сохраняется после снижения max-age. Пограничные узлы CDN и браузерные кэши удерживают ответы до истечения исходного max-age. Снижение значения на источнике не очищает уже закэшированные копии. После изменения времени жизни кэша выполните очистку CDN. В Cloudflare очищайте по URL или выполните полную очистку зоны. В CloudFront создайте инвалидацию для затронутых путей.
Браузер показывает старую страницу при навигации назад/вперёд несмотря на no-store. Кэш навигации назад/вперёд (bfcache) в Chrome, Firefox и Safari восстанавливает полное состояние страницы из памяти в обход no-store. Это поведение задумано намеренно и не является нарушением правил кэширования. Проверьте заголовок Cache-Control во вкладке Network в DevTools со снятым флажком “Disable cache”, чтобы убедиться, что заголовок доходит до браузера. Добавление обработчиков события unload отключает bfcache в некоторых браузерах, но ухудшает производительность.
Заголовок Expires конфликтует с Cache-Control. Когда присутствуют оба заголовка, Cache-Control: max-age имеет приоритет над Expires. Удалите заголовок Expires, чтобы избежать путаницы. В nginx:
expires off;
add_header Cache-Control "max-age=3600";
В Apache:
Header unset Expires
Header set Cache-Control "max-age=3600"
Диагностика заголовков кэширования с помощью curl и DevTools. Выполните curl -I https://example.re, чтобы изучить заголовки ответа от источника. Добавьте -H “Cache-Control: no-cache”, чтобы обойти кэши CDN и обратиться напрямую к источнику. В DevTools браузера откройте вкладку Network, выберите запрос и просмотрите раздел Response Headers. Обратите внимание на заголовки Age, X-Cache и CF-Cache-Status, чтобы определить, пришёл ли ответ с пограничного узла CDN или от источника.