HTTP-кэширование часто описывают слишком просто: браузер «сохранил файл» и потом взял его локально. На практике решение состоит из нескольких отдельных проверок. Сначала нужно понять, разрешено ли сохранять ответ, затем — остаётся ли сохранённая копия свежей, а если срок свежести закончился — можно ли быстро проверить её у исходного сервера без повторной передачи всего тела ответа.
Поэтому Cache-Control, ETag и Last-Modified решают разные задачи. Cache-Control задаёт правила хранения и повторного использования ответа, а ETag и Last-Modified помогают проверить, изменилась ли уже сохранённая версия. Если отдельно нужно разобраться, как сервер сообщает клиенту тип данных в теле ответа, это связано с другим заголовком — Content-Type и MIME-типами.
Полезнее думать не о «кэше», а о четырёх решениях подряд
При повторном обращении к одному URL клиенту или промежуточному кэшу нужно ответить примерно на четыре вопроса.
- Можно ли хранить этот ответ? На это влияют метод запроса, статус ответа и директивы кэширования.
- Если ответ уже сохранён, он ещё свежий? Пока срок свежести не закончился, подходящую копию можно использовать без обращения к исходному серверу.
- Если копия устарела, можно ли её проверить? Для этого используются валидаторы, прежде всего
ETagиLast-Modified. - Что ответил сервер на проверку? Если представление не изменилось, сервер может вернуть
304 Not Modifiedбез нового тела. Если изменилось — приходит новый обычный ответ, например200 OKс актуальным содержимым.
Эта последовательность важнее запоминания отдельных директив. Она сразу показывает, почему «хранить», «использовать без проверки» и «проверить актуальность» — не одно и то же действие.
Cache-Control управляет повторным использованием ответа
Поле Cache-Control содержит директивы для кэшей. Одна из самых понятных — max-age: она задаёт, сколько секунд после генерации ответа тот может считаться свежим в соответствующем кэше.
Cache-Control: max-age=3600
Такой заголовок не означает, что ресурс обязательно будет храниться ровно час и гарантированно останется в памяти браузера. Он сообщает, что подходящую сохранённую копию можно считать свежей в пределах заданного времени. Кэш может удалить запись раньше, например из-за собственных ограничений хранения.
У нескольких часто используемых директив принципиально разные значения:
| Директива | Что означает на практике | Что важно не перепутать |
|---|---|---|
max-age=N |
Задаёт срок свежести ответа в секундах | Это не гарантия фактического хранения записи всё это время |
no-cache |
Разрешает хранить ответ, но перед повторным использованием требует успешной проверки, когда директива применима | Название не означает «никогда не сохранять» |
no-store |
Запрещает кэшу сохранять ответ | Это более жёсткое правило, чем no-cache |
private |
Разрешает хранение приватным кэшем, например браузером конкретного пользователя, но запрещает хранение общим кэшем | Особенно важно для персонализированных ответов |
public |
Явно разрешает хранение ответа общими кэшами при соблюдении остальных условий | Не делает любой ответ автоматически безопасным для общего кэша |
must-revalidate |
После устаревания требует проверить сохранённый ответ перед обычным повторным использованием | Не задаёт срок свежести само по себе |
Свежий ответ и проверенный ответ — разные состояния
Если сохранённый ответ ещё свежий, кэш может использовать его без запроса к исходному серверу. Это наиболее дешёвый вариант: сеть вообще не нужна для получения тела ресурса.
Когда срок свежести закончился, сохранённая копия не обязана сразу выбрасываться. Она может стать кандидатом на повторную проверку. Здесь как раз появляются валидаторы.
Главная практическая разница выглядит так:
свежая копия
→ использовать сразу
устаревшая копия + валидатор
→ условный запрос к серверу
→ 304 Not Modified или новый 200 OK
устаревшая копия без пригодной проверки
→ получить актуальный ответ заново
Поэтому наличие сетевого запроса ещё не означает, что кэширование «не работает». Повторная проверка может обращаться к серверу, но при неизменившемся ресурсе не передавать его тело повторно.
ETag проверяет версию представления
ETag — это непрозрачный идентификатор текущего представления ресурса. Формат значения определяет сервер; клиенту не нужно знать, является ли внутри хэш, номер версии или другой маркер. Важно только, чтобы сервер последовательно сравнивал значения по правилам HTTP.
HTTP/1.1 200 OK
Cache-Control: max-age=600
ETag: "v42"
Когда сохранённый ответ потребует проверки, клиент может отправить известное значение через If-None-Match:
GET /app.css HTTP/1.1
If-None-Match: "v42"
Если выбранное представление по-прежнему соответствует этому тегу, сервер может ответить:
HTTP/1.1 304 Not Modified
ETag: "v42"
Тело ресурса заново не передаётся: клиент продолжает использовать уже сохранённую копию. Если версия изменилась, сервер возвращает актуальное представление обычным ответом.
У ETag есть сильная и слабая форма. Слабый тег помечается префиксом W/ и означает, что представления могут считаться эквивалентными для слабого сравнения, даже если они не идентичны побайтно. Это различие важно не во всех сценариях кэширования, но его нельзя игнорировать там, где требуется точное сравнение представлений или работа с диапазонами байтов.
Last-Modified использует время последнего изменения
Last-Modified сообщает дату и время, когда исходный сервер считает выбранное представление изменённым в последний раз.
Last-Modified: Sun, 06 Sep 2026 10:30:00 GMT
При повторной проверке клиент может передать это значение через If-Modified-Since:
GET /manual.pdf HTTP/1.1
If-Modified-Since: Sun, 06 Sep 2026 10:30:00 GMT
Если более новой версии нет, возможен ответ 304 Not Modified. Если представление изменилось, сервер отправит новую версию.
Last-Modified удобен тем, что может естественно опираться на дату изменения файла или другого источника данных. Но временная метка не всегда различает быстрые изменения достаточно точно. Поэтому HTTP отдельно предусматривает entity tags, а сервер при возможности может отправлять оба валидатора.
Если есть и ETag, и Last-Modified
В одном ответе могут присутствовать оба поля:
HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "page-184"
Last-Modified: Sun, 06 Sep 2026 10:30:00 GMT
При последующей проверке клиент может отправить и If-None-Match, и If-Modified-Since. Для проверки кэшированного ответа условие с If-None-Match имеет приоритет перед If-Modified-Since. Это позволяет использовать более точный валидатор, не отказываясь от даты последнего изменения там, где она также полезна.

Почему no-cache и no-store нельзя использовать как синонимы
Это одна из самых частых ошибок в конфигурации.
Cache-Control: no-cache не запрещает сохранить ответ. Смысл в другом: сохранённую копию нельзя просто считать пригодной к использованию без предусмотренной проверки. В сочетании с ETag или Last-Modified это даёт удобный режим для документов, которые должны оставаться актуальными, но при этом не требуют повторной передачи полного тела, если ничего не изменилось.

Cache-Control: no-store, напротив, запрещает хранить ответ в кэше. Такой режим нужен не «для надёжности по умолчанию», а когда хранение конкретного ответа действительно нежелательно.
Если поставить no-store на всё подряд, валидаторы уже не дадут обычной выгоды повторного использования сохранённого ответа, потому что сохранять его запрещено. Поэтому директиву стоит выбирать от свойств конкретного ресурса, а не из-за похожего названия.
HTML и версионированные ресурсы часто требуют разных политик
Одна универсальная настройка для всех URL обычно неудобна. Например, основной HTML-документ может менять содержимое, сохраняя тот же адрес. Для такого ресурса часто разумна политика, при которой клиент проверяет актуальность и при неизменившейся версии получает 304.
Cache-Control: no-cache
ETag: "page-184"
Last-Modified: Sun, 06 Sep 2026 10:30:00 GMT
У статических CSS, JavaScript или изображений ситуация может быть другой. Если в URL встроена версия или отпечаток содержимого и при каждом изменении файла создаётся новый URL, старую версию можно кэшировать намного дольше: изменение самого адреса становится механизмом получения новой версии.
/assets/app.8f31c2.js
Cache-Control: public, max-age=31536000, immutable
Ключевое условие здесь не число секунд само по себе, а договорённость: содержимое такого URL после публикации не меняют. Если под тем же адресом незаметно заменить файл, длинный срок свежести может оставить часть клиентов со старой копией до окончания её срока или удаления записи из кэша.
Персонализированные ответы требуют отдельной проверки
Ответ, который зависит от авторизации, cookies или данных конкретного пользователя, нельзя механически обрабатывать как общий статический ресурс. Для таких страниц сначала нужно определить, допускается ли хранение вообще и каким кэшем. Директива private позволяет ограничить хранение приватным кэшем пользователя, но выбор политики зависит от содержания и требований конкретного приложения.
Особенно опасно автоматически считать, что public или большой max-age безопасны для любого ответа только потому, что они ускоряют повторную загрузку. Перед настройкой общего кэша нужно проверить, не меняется ли представление в зависимости от пользователя, заголовков запроса или другого контекста.
Как диагностировать кэширование по фактическому обмену
Начинать лучше не с конфигурационного файла сервера, а с ответа, который реально получает клиент.
- Откройте нужный URL в панели Network браузера и посмотрите
Cache-Control,ETag,Last-Modified, статус и фактический источник ответа. - Проверьте первый ответ: можно ли понять срок свежести и есть ли валидатор.
- Повторите запрос после того, как копия должна потребовать проверки.
- Посмотрите, появился ли
If-None-MatchилиIf-Modified-Since. - Проверьте результат:
304 Not Modifiedпри неизменившейся версии или новый200 OKс актуальным телом. - Отдельно проверьте, что персонализированные ответы не попадают в общий кэш.
Для быстрой проверки заголовков пригодится и командная строка:
curl -I https://example.com/page
curl -I \
-H 'If-None-Match: "v42"' \
https://example.com/page
Конкретный результат зависит от конфигурации сервера, промежуточных прокси и текущего состояния ресурса. Поэтому наличие 304 не стоит проверять по одному заранее придуманному сценарию: сначала нужно посмотреть реальные заголовки первого ответа и только потом строить условный запрос.
Ошибки, которые проще увидеть по этой схеме
no-cacheтрактуют как запрет хранения. В результате диагностика строится на неверной модели.- Длинный
max-ageставят на URL, содержимое которого меняют без смены адреса. Клиент не обязан узнавать о новой версии, пока старая копия остаётся свежей. - ETag меняется без содержательного изменения ресурса. Тогда повторная проверка теряет часть смысла: сервер постоянно считает сохранённую версию другой.
- Last-Modified формируется нестабильно. Если дата не отражает реальное изменение представления, условные запросы дают ненадёжный результат.
- Смешивают браузерный кэш, общий прокси-кэш и прикладные механизмы хранения. У них разные области действия, поэтому один и тот же ответ нужно оценивать в конкретном контексте.
- Проверяют только конфигурацию сервера. Между приложением и пользователем могут быть reverse proxy, CDN или другие промежуточные компоненты; важен итоговый HTTP-ответ.
Короткая схема выбора
| Ситуация | Что проверить в первую очередь |
|---|---|
| Ресурс может долго жить под неизменяемым версионированным URL | Длительный срок свежести и смена URL при каждом изменении содержимого |
| URL остаётся прежним, но содержимое может обновляться | Политика свежести плюс ETag и/или Last-Modified для повторной проверки |
| Ответ персонализирован | Разрешено ли хранение и должен ли доступ иметь только приватный кэш |
| Ответ вообще нельзя сохранять | Нужна ли директива no-store |
| Есть жалоба «браузер показывает старую версию» | Фактические Cache-Control, возраст копии, валидаторы, URL ресурса и результат условного запроса |
Итог
HTTP-кэширование проще диагностировать, если разделять три вопроса: можно ли сохранить ответ, можно ли использовать его без сети и можно ли проверить сохранённую версию без повторной передачи всего ресурса. Cache-Control в основном управляет правилами хранения и свежести, а ETag и Last-Modified дают серверу и клиенту механизм повторной проверки. Ответ 304 Not Modified — не «особый кэш сам по себе», а результат успешного условного запроса, после которого клиент продолжает использовать уже имеющееся представление.
Поэтому при проблемах с кэшированием полезно смотреть не на одну директиву, а на всю цепочку: исходный ответ → срок свежести → валидатор → условный запрос → итоговый статус. Именно в этой последовательности обычно видно, на каком уровне возникло несоответствие.
Источники
- RFC 9111 — HTTP Caching.
- RFC 9110 — HTTP Semantics: валидаторы
ETag,Last-Modifiedи условные запросы. - MDN — HTTP-кэширование.
- MDN — условные HTTP-запросы.
- web.dev — Prevent unnecessary network requests with the HTTP Cache.