Главная / База знаний / HTTP-заголовки кэширования: Cache-Control, ETag, Last-Modified
инструкция · 31 октября 2026

HTTP-заголовки кэширования: Cache-Control, ETag, Last-Modified

Как браузеры и прокси принимают решение о кэшировании на основе HTTP-заголовков, чем условные запросы отличаются от полного повторного скачивания, и как настроить это правильно в nginx.

Зачем вообще нужны заголовки кэширования

Каждый раз, когда браузер загружает страницу, он получает десятки отдельных ресурсов — HTML, CSS, JavaScript, шрифты, изображения, — и без явных инструкций от сервера единственный способ гарантированно получить актуальную версию каждого из них — скачать всё заново при каждом визите. HTTP-заголовки кэширования — это способ сервера явно сказать браузеру и промежуточным узлам сети (прокси, CDN), сколько времени можно безопасно использовать уже сохранённую копию ресурса без повторного обращения к серверу вообще.

Правильно настроенное кэширование сокращает не только время загрузки повторных визитов для конкретного пользователя, но и реальную нагрузку на сервер: если статические ресурсы отдаются с длинным временем жизни кэша, повторные посетители не создают для сервера ни одного нового запроса на эти файлы вплоть до следующего обновления версии, что особенно заметно на сайтах с большим количеством возвращающихся посетителей или высокой посещаемостью в целом.

В этой статье разбираются директивы Cache-Control, механизм условных запросов через ETag и Last-Modified, практическая настройка в nginx для разных типов контента и техника cache-busting для безопасного обновления версии ресурсов без ожидания истечения старого кэша.

Cache-Control: основная директива управления кэшем

Cache-Control — основной и наиболее гибкий заголовок управления кэшированием в современном HTTP, пришедший на смену устаревшему Expires. Он принимает список директив через запятую, и от их комбинации зависит, кто именно может кэшировать ответ, на какой срок и при каких условиях обязана происходить повторная проверка актуальности.

Заголовок для статического файла с версионированным именем — кэшировать год, содержимое никогда не изменится
Cache-Control: public, max-age=31536000, immutable

Директива max-age задаёт время жизни кэшированной копии в секундах, отсчитываемое с момента получения ответа — по истечении этого срока кэш считается устаревшим и должен быть либо перепроверен, либо перезагружен. Директива public явно разрешает кэширование любым разделяемым кэшем (CDN, прокси провайдера), а private ограничивает кэширование только приватным хранилищем конкретного браузера — это важно для ответов, которые формируются с учётом авторизации конкретного пользователя и не должны попасть в общий кэш, доступный другим посетителям через тот же CDN или корпоративный прокси.

Ответ для авторизованного пользователя, требующий проверки перед использованием, и ответ, который нельзя сохранять вообще
Cache-Control: private, no-cache
Cache-Control: no-store

no-cache — вопреки интуитивному названию — не запрещает сохранение копии, а обязывает перед каждым использованием сначала отправить условный запрос серверу для проверки актуальности; если сервер подтвердит, что содержимое не изменилось (ответ 304), браузер использует уже сохранённую копию без повторной передачи тела. no-store — куда более строгая директива, полностью запрещающая сохранение ответа в любом кэше, что уместно для страниц с действительно чувствительными данными вроде банковских операций или персональных данных пользователя.

ETag и условные запросы If-None-Match

ETag (entity tag) — заголовок ответа, содержащий идентификатор конкретной версии содержимого ресурса, обычно рассчитываемый как хэш от содержимого файла или иное значение, гарантированно меняющееся при изменении контента. В отличие от даты изменения, ETag не зависит от точности часов сервера или от того, действительно ли изменилось содержимое файла при его пересборке с идентичным результатом.

Цикл условного запроса с использованием ETag
HTTP/1.1 200 OK
ETag: "5d8c72a5edda3"
Cache-Control: no-cache

# при следующем запросе браузер отправляет:
If-None-Match: "5d8c72a5edda3"

# если содержимое не изменилось, сервер отвечает без тела:
HTTP/1.1 304 Not Modified

Механизм работает так: получив ответ с ETag, браузер сохраняет это значение вместе с копией ресурса. При следующем запросе того же ресурса (если кэш требует проверки, например из-за no-cache или истечения max-age) браузер отправляет заголовок If-None-Match с сохранённым значением ETag. Если содержимое на сервере с тех пор не изменилось и его текущий ETag совпадает, сервер отвечает статусом 304 Not Modified без передачи тела ответа вообще — экономится не только время, но и весь объём трафика на пересылку неизменившегося содержимого.

Этот же механизм условной проверки применим и к самому запросу на изменение — заголовок If-Match используется, например, при обновлении ресурса через API, чтобы гарантировать, что клиент модифицирует именно ту версию данных, которую он последний раз читал, и предотвратить случайную перезапись более свежих изменений, внесённых кем-то другим между чтением и записью.

Last-Modified и If-Modified-Since как более простая альтернатива

Last-Modified — более старый и простой механизм условной проверки, основанный на дате и времени последнего изменения ресурса, а не на хэше содержимого. Сервер возвращает эту дату в заголовке ответа, а браузер при следующем запросе отправляет её обратно в заголовке If-Modified-Since — сервер сравнивает переданную дату с реальным временем последнего изменения файла и отвечает либо 304 без тела, либо полным содержимым с новой датой в Last-Modified.

Условный запрос на основе даты последнего изменения
HTTP/1.1 200 OK
Last-Modified: Tue, 15 Sep 2026 08:12:44 GMT

# следующий запрос браузера:
If-Modified-Since: Tue, 15 Sep 2026 08:12:44 GMT

У Last-Modified есть два практических ограничения по сравнению с ETag. Во-первых, точность указанной даты — до секунды, и если файл на сервере многократно перезаписывается с тем же самым содержимым (например, при автоматической пересборке ассетов без реальных изменений исходного кода), дата изменится, а содержимое — нет, что приведёт к ненужной повторной передаче. Во-вторых, на распределённой инфраструктуре с несколькими серверами, отдающими один и тот же файл, время модификации на файловой системе разных серверов может незначительно отличаться, тогда как ETag, рассчитанный от содержимого, остаётся одинаковым независимо от того, какой именно сервер обработал запрос.

На практике оба механизма часто используются одновременно — сервер отдаёт и ETag, и Last-Modified, а браузер предпочитает более точный ETag при наличии обоих значений, используя If-Modified-Since как запасной вариант для более старых клиентов или прокси, которые не работают с ETag.

Настройка кэширования статики в nginx

Для статических файлов (изображения, CSS, JavaScript, шрифты) типичная практика — назначить длинный срок жизни кэша через блок location с проверкой расширения файла, поскольку такие файлы меняются гораздо реже HTML-страниц и в идеале должны иметь версионированное имя при каждом реальном изменении содержимого.

Настройка длинного кэша для статических ресурсов в nginx
location ~* \.(css|js|woff2?|ttf|svg|jpg|jpeg|png|webp|gif|ico)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    access_log off;
}

Директива expires в nginx — это удобный сокращённый способ одновременно выставить заголовок Expires (устаревший, но всё ещё поддерживаемый некоторыми старыми клиентами) и добавить в ответ подходящее значение Cache-Control с max-age, рассчитанным на основе указанного интервала. Директива immutable, добавленная явно через add_header, дополнительно сообщает браузеру, что содержимое по этому URL никогда не изменится в течение всего срока жизни кэша, — это позволяет браузеру пропустить даже фоновую проверку актуальности при обновлении страницы, которую он иначе мог бы выполнить из осторожности.

HTML-документ всегда проверяется на актуальность перед использованием
location = /index.html {
    add_header Cache-Control "no-cache, must-revalidate";
}

Для самого HTML-документа, в отличие от подключаемых статических файлов, обычно нужна ровно обратная стратегия: короткое или нулевое время жизни кэша с обязательной проверкой актуальности через условный запрос, поскольку именно HTML определяет, какие версии CSS и JavaScript должны быть подключены — если закэшировать HTML надолго, пользователь может продолжать получать ссылки на уже удалённые или изменившиеся версии подключаемых ресурсов.

Cache-busting: обновление версии без ожидания истечения кэша

Длинный срок жизни кэша для статических ресурсов создаёт очевидную проблему: как заставить браузер скачать новую версию файла, если старая версия всё ещё считается действительной согласно ранее выданному Cache-Control с большим max-age? Решение называется cache-busting — изменение самого URL ресурса при каждом реальном изменении его содержимого, чтобы браузер воспринимал новую версию как совершенно другой, ранее не встречавшийся ресурс.

Есть два распространённых подхода к формированию нового URL. Первый — добавление query-параметра с версией или хэшем содержимого к неизменному имени файла: style.css?v=a1b2c3. Второй — включение хэша содержимого прямо в имя файла: style.a1b2c3.css. Второй подход считается более надёжным, поскольку некоторые старые версии прокси и CDN по умолчанию не учитывают query-строку при формировании ключа кэша и могут отдать закэшированную версию по старому query-параметру, тогда как изменение самого имени файла гарантированно создаёт новый, никогда ранее не запрашивавшийся URL.

Изменение хэша в имени файла при пересборке ассетов
<!-- до пересборки -->
<link rel="stylesheet" href="/assets/style.3f8a21.css">

<!-- после изменения содержимого файла -->
<link rel="stylesheet" href="/assets/style.9c02e7.css">

Современные сборщики фронтенда (Webpack, Vite и подобные) автоматически рассчитывают хэш содержимого и подставляют его в имя выходного файла при каждой сборке, избавляя от необходимости делать это вручную — задача администратора сервера в этом случае сводится к тому, чтобы убедиться, что nginx действительно назначает длинный immutable-кэш именно версионированным файлам, а HTML-документ, ссылающийся на них, кэшируется коротко или не кэшируется вовсе.

Кэширование на уровне CDN и промежуточных прокси

Заголовки Cache-Control влияют не только на браузер конечного пользователя, но и на промежуточные разделяемые кэши — прокси интернет-провайдера, корпоративный прокси и, что особенно важно для производительности сайта, CDN. Директивы public и private напрямую определяют, разрешено ли такому разделяемому кэшу сохранять копию ответа для последующей раздачи другим пользователям, а не только тому, кто изначально сделал запрос.

Отдельная директива, важная именно для разделяемых кэшей, — s-maxage: она задаёт время жизни кэша конкретно для CDN и других общих кэшей, которое может отличаться от max-age, применяемого браузером. Это позволяет, например, настроить относительно короткое время жизни в браузере пользователя (чтобы он быстрее увидел обновление после ручной инвалидации) при более длинном времени жизни на уровне CDN, где инвалидация кэша при необходимости выполняется явным запросом к API CDN, а не пассивным ожиданием истечения срока.

Разное время жизни кэша для браузера (минута) и для CDN (час)
Cache-Control: public, max-age=60, s-maxage=3600

Многие CDN дополнительно поддерживают собственные заголовки управления кэшем помимо стандартных HTTP-директив (например, свои варианты Surrogate-Control), а также API для принудительной инвалидации (purge) конкретных URL — это особенно полезно, когда контент изменился раньше срока истечения назначенного кэша и ждать естественного истечения нежелательно, например при срочном исправлении опубликованной ошибки на статической странице.

Заголовок Vary и stale-while-revalidate

Заголовок Vary сообщает кэшу, что ответ на один и тот же URL может отличаться в зависимости от значения другого заголовка запроса, и кэш обязан хранить отдельную копию для каждого встретившегося значения этого заголовка, а не одну общую. Самый частый практический случай — Vary: Accept-Encoding, поскольку сервер может отдавать один и тот же ресурс в сжатом Gzip или Brotli виде и в несжатом, в зависимости от того, что браузер заявил в своём заголовке Accept-Encoding, и без Vary кэш рискует один раз сохранить сжатую версию и отдать её клиенту, который сжатие не поддерживает.

Кэш должен различать сжатую и несжатую версии одного и того же URL
Vary: Accept-Encoding
Content-Encoding: br

Ещё один практически полезный, хотя и менее известный, набор директив — stale-while-revalidate и stale-if-error, поддерживаемые современными браузерами и рядом CDN. Первая разрешает кэшу немедленно отдать уже устаревшую копию ресурса пользователю, одновременно в фоне запросив у сервера свежую версию для последующих запросов — это устраняет задержку ожидания ответа сервера ценой того, что один конкретный запрос может получить копию, устаревшую на несколько секунд. Вторая директива разрешает отдать устаревшую копию, если сервер в момент запроса недоступен или вернул ошибку, что повышает отказоустойчивость сайта при кратковременных сбоях бэкенда.

Минуту кэш считается полностью свежим, ещё десять минут можно отдавать устаревшую копию с фоновым обновлением, а при ошибке сервера — сутки
Cache-Control: max-age=60, stale-while-revalidate=600, stale-if-error=86400

Эти директивы особенно ценны для страниц с умеренно динамичным содержимым (например, витрина каталога товаров с ценами, обновляемыми раз в несколько минут), где абсолютная свежесть каждого отдельного запроса не критична, но важно избежать заметной задержки или полного отказа при недоступности бэкенда на короткое время.

Типичные ошибки при настройке кэширования

Наиболее частая ошибка — назначение длинного Cache-Control с большим max-age на HTML-документ страницы вместе со статикой, из общих соображений «закэшировать всё для скорости». Результат — пользователи продолжают видеть старую версию страницы (а вместе с ней и ссылки на старые версии CSS и JavaScript) значительно дольше, чем ожидалось, и единственный способ форсировать обновление — жёсткая перезагрузка страницы, что для обычного посетителя сайта неочевидно и не является ожидаемым действием.

Вторая частая ошибка — обратная: полное отсутствие кэширования для статических ресурсов «на всякий случай», из опасения, что пользователь увидит устаревшую версию файла. При правильно настроенном cache-busting эта опасность отсутствует в принципе — изменение содержимого файла всегда сопровождается изменением его URL, поэтому длинный immutable-кэш для версионированной статики абсолютно безопасен и не связан с риском показать устаревшее содержимое.

Третья ошибка — конфликтующие заголовки, устанавливаемые на разных уровнях стека: например, само приложение (например, PHP-фреймворк) выставляет один набор заголовков Cache-Control, а конфигурация nginx поверх него — другой, и итоговое поведение браузера зависит от порядка обработки и того, какая директива в итоге окажется в ответе. Стоит явно проверять фактические заголовки в инструментах разработчика браузера после любого изменения в конфигурации, а не полагаться на то, что настройка одного уровня стека автоматически отменяет настройку другого.

почему мы

Почему стоит выбрать нас

Быстрый запуск

Подберём конфигурацию и развернём проект в день обращения.

Надёжность

NVMe-диски, защита от DDoS и резервные копии по расписанию.

Поддержка 24/7

Дежурные инженеры на связи в любое время суток.

Настройка под ключ

Установим CMS, настроим веб-сервер, SSL и почту за вас.

Перенос без простоя

Аккуратно перенесём сайт и базу данных на новый сервер.

Прозрачные цены

Актуальные тарифы и честный подбор без навязывания.

частые вопросы

Вопросы и ответы

В чём разница между Cache-Control: no-cache и no-store?

no-cache разрешает браузеру сохранить копию ресурса, но обязывает перед каждым использованием проверить у сервера через условный запрос, не устарела ли она — при отсутствии изменений сервер вернёт 304 без передачи тела. no-store запрещает сохранение копии вообще, ресурс каждый раз скачивается заново целиком и нигде не кэшируется, что нужно для по-настоящему чувствительных данных.

Что произойдёт, если не указать Cache-Control вообще?

Поведение станет зависеть от эвристик конкретного браузера и промежуточных прокси, которые могут принять собственное решение о времени жизни кэша на основе заголовка Last-Modified или вовсе не кэшировать ответ — на практике это означает непредсказуемое и не контролируемое вами поведение, поэтому явное указание Cache-Control предпочтительнее в любой ситуации.

Зачем нужен ETag, если уже есть Last-Modified?

Last-Modified имеет точность до секунды и опирается на время изменения файла, что не всегда отражает реальное изменение содержимого (например, при пересборке файла с тем же самым содержимым дата изменится, а контент — нет). ETag — это хэш или другой идентификатор содержимого, который остаётся неизменным, пока содержимое действительно не изменилось, и потому даёт более точный сигнал для условных запросов.

Что такое cache-busting и зачем добавлять хэш к именам файлов?

Это техника обхода долгого кэша при обновлении файла: если статическому ресурсу назначен Cache-Control с большим max-age, браузер не станет проверять его на сервере вплоть до истечения срока — включение хэша содержимого в имя файла (style.a1b2c3.css) гарантирует, что при изменении содержимого поменяется и URL, а значит, браузер безусловно скачает новую версию, не дожидаясь истечения старого кэша.

Может ли CDN кэшировать страницу, если на ней стоит Cache-Control: private?

Нет, директива private как раз и означает, что ответ предназначен для одного конкретного пользователя и не должен сохраняться разделяемыми кэшами вроде CDN или корпоративного прокси — такие ответы допустимо кэшировать только в приватном хранилище самого браузера пользователя, если это разрешено другими директивами.

Что означает must-revalidate в Cache-Control?

Директива обязывает кэш строго соблюдать указанное время жизни (max-age) и после его истечения обязательно обратиться к серверу за проверкой актуальности перед использованием сохранённой копии, даже если по каким-то причинам (например, недоступность сервера) кэш в других обстоятельствах мог бы отдать устаревшую копию как временную меру.

Как проверить, что кэширование реально работает, а не просто настроено в конфиге?

В инструментах разработчика браузера, на вкладке Network, при повторной загрузке страницы нужно смотреть колонку Status и Size — запрос со статусом 304 или пометкой from disk cache/memory cache и без реальной передачи тела ответа подтверждает, что кэширование действительно применяется, а не просто присутствует в заголовках ответа сервера.

Как оплачивать услуги?

Оплата помесячная, для облачных серверов доступна почасовая тарификация. Принимаются карты, банковские переводы и электронные кошельки.

Есть ли круглосуточная поддержка?

Да, дежурные инженеры на связи 24/7.

Выдаёте ли закрывающие документы?

Да, предоставим необходимые документы для оплаты.

Как оставить заявку?

Заполните форму на странице или нажмите «Заказать» — мы свяжемся и поможем с подбором.

заявка

Оставьте заявку

Ответим и поможем с подбором и настройкой.