Я довольно часто вижу одну и ту же картину: сайт вроде бы уже на PHP 8.2 или 8.3, картинки в WebP, шрифты поджаты, CDN подключен, а в ответах сервера — полный бардак с заголовками Cache-Control, ETag и Last-Modified. И честно говоря, это одна из тех мелочей, которые легко игнорировать, а потом удивляться, почему браузер снова качает тяжёлые файлы, PageSpeed пляшет на 5–10 пунктов, а сервер грузится сильнее, чем должен.
В 2026 году я бы настраивал эти заголовки не “для галочки”, а как часть нормальной технической базы. Особенно если у вас WordPress, Битрикс или Laravel-проект. И да, если сайт уже требует доработки сайта, то правильная настройка кэширования — почти всегда одна из первых задач, которые стоит закрыть.
Что такое Cache-Control, ETag и Last-Modified
Если говорить грубо, это три способа сказать браузеру и промежуточным кэшам: “не тащи файл заново, если он не изменился” или “можно хранить это столько-то времени”. На деле они решают разные задачи, и путать их — плохая идея. Я видел проекты, где разработчик поставил огромный max-age на HTML, а потом удивлялся, почему у пользователей неделями не обновляется шапка сайта после редизайна.
Cache-Control — это главный и самый современный заголовок управления кэшем. Он отвечает за срок жизни, поведение в браузере и на прокси. ETag — это идентификатор версии ресурса. Браузер потом присылает его обратно в If-None-Match, а сервер решает: файл тот же или уже изменился. Last-Modified — дата последнего изменения, и браузер использует её через If-Modified-Since.
На моей практике чаще всего достаточно правильно настроенного Cache-Control плюс либо ETag, либо Last-Modified. Но выбор зависит от типа ресурса и архитектуры. Например, для статики на CDN я обычно делаю упор на Cache-Control и версионирование файлов, а для HTML-страниц — на короткий кэш и условные запросы.
И ещё один момент. Если вы читали мою статью про кэширование статики, CDN и заголовки Cache-Control, то здесь логика будет очень похожая, но я разберу тему глубже и с упором именно на 2026 год. Потому что сейчас браузеры, CDN и серверные стеки ведут себя уже не так, как пять лет назад.
Когда нужно настраивать эти заголовки, а когда лучше не лезть
Самая частая ошибка — одинаково кэшировать вообще всё. Так делать нельзя. Статические файлы — да, HTML — только аккуратно, API — отдельно, личный кабинет — почти всегда без агрессивного кэша. Если у вас интернет-магазин на Битрикс или WooCommerce, то неправильный Cache-Control может сломать корзину, фильтры или авторизацию.
Я обычно делю сайт на несколько зон: статические ассеты, публичные HTML-страницы, динамика для авторизованных пользователей, API и служебные файлы. Для каждой зоны нужен свой подход. Например, CSS и JS можно кэшировать на год, если у вас есть хэш в имени файла. А вот HTML лучше кэшировать на 30–300 секунд или вообще переваливать через CDN с revalidate.
Если у сайта слабая производительность, сначала я смотрю не на заголовки, а на архитектуру. Бывает, что проблема вообще в MySQL 5.7, кривом кеше CMS или в отсутствии OPcache. Тогда одна настройка Cache-Control не спасёт. В таких случаях нужна нормальная проверка сайта, чтобы понять, где узкое место: сервер, код, CDN или фронтенд.
У одного клиента в 2025 году был WordPress-магазин с настроенным Nginx, и они поставили Cache-Control: public, max-age=31536000 на всё через .htaccess после переезда. Итог был неприятный: логотип обновили, а пользователи три дня видели старый вариант, пока не сбросили кэш вручную. И это не “мелкий баг”, это уже вопрос доверия к сайту.
Cache-Control: какие директивы использовать в 2026
Если коротко, я чаще всего использую несколько директив: public, private, no-store, no-cache, max-age, s-maxage, must-revalidate, stale-while-revalidate и stale-if-error. Но важно понимать смысл, а не просто копировать готовый набор из статьи в статью.
Для статики — изображения, CSS, JS, шрифты — обычно ставлю длинный срок жизни. Если файл версионируется через хэш в имени, например style.4f2a9c.css, то можно спокойно ставить Cache-Control: public, max-age=31536000, immutable. Для HTML страниц я люблю более скромный режим: max-age=0, must-revalidate или короткий max-age в связке с ETag.
И тут есть нюанс. В 2026 году браузеры уже достаточно умные, но промежуточные кэши, CDN и reverse proxy всё ещё могут жить своей жизнью. Поэтому для публичного контента полезно разделять browser cache и shared cache. Тут как раз помогает s-maxage: браузер может хранить одно, а CDN — другое.
# Nginx: базовая схема для статики и HTML
location ~* \.(?:css|js|woff2|woff|ttf|eot|png|jpg|jpeg|gif|svg|webp|avif)$ {
expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable" always;
access_log off;
log_not_found off;
}
location / {
add_header Cache-Control "no-cache, must-revalidate" always;
try_files $uri $uri/ /index.php?$args;
}
Этот пример рабочий, но я бы не ставил его в прод без проверки. Почему? Потому что в реальном проекте могут быть исключения: админка, AJAX, API, страницы поиска, корзина, личный кабинет. Особенно в Битрикс и WordPress надо быть аккуратным. Если хотите не сломать проект, лучше сначала посмотреть конфигурацию и уже потом вносить правки — иногда это требует нормальной поддержки Битрикс или поддержки WordPress, а не “быстрого фиксика на коленке”.
Ещё одно замечание. Если вы используете CDN, например Cloudflare, Fastly или Bunny CDN, проверьте, не переписывает ли он ваши заголовки. У меня был случай, когда в Nginx стоял правильный Cache-Control, а на уровне CDN он заменялся на дефолтный. В итоге в Lighthouse всё выглядело нормально, а пользователи продолжали получать странное поведение кэша.
ETag: когда это полезно, а когда лишнее
ETag — штука полезная, но не всегда нужная. Он хорош, когда сервер может быстро и точно определить, изменилась ли сущность. Для маленького сайта это удобно: браузер прислал If-None-Match, сервер увидел, что тег тот же, и вернул 304 Not Modified вместо полной страницы. Экономия трафика есть, и иногда вполне заметная.
Но на деле ETag часто создаёт больше вопросов, чем пользы. Например, в кластере из нескольких серверов ETag может отличаться между нодами, если он завязан на inode или внутренние параметры файла. Тогда один и тот же ресурс на разных узлах получает разные ETag, а кэш начинает вести себя нестабильно. Я такое видел на проектах с Nginx и несколькими backend-серверами за балансировщиком.
Если у вас статический сайт или простой сервер с одним узлом, ETag можно использовать. Но если есть CDN, масштабирование, Docker-контейнеры или несколько фронтов — я чаще отключаю ETag для статики и опираюсь на versioned filenames плюс Cache-Control. Это проще, надёжнее и предсказуемее.
# Nginx: отключение ETag, если используете версионирование файлов
etag off;
location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|webp|avif|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable" always;
}
Есть и обратная ситуация. У одного клиента на Laravel 11 и PHP 8.3 API возвращал JSON-список товаров. Ответы были короткие, данные обновлялись раз в 10–15 минут, а трафик — приличный. Там ETag отлично помог: браузеры и промежуточный кэш перестали тянуть полный JSON каждый раз, а сервер разгрузился. То есть ETag полезен именно там, где контент часто запрашивают, но редко меняют.
Но не путайте ETag с защитой от логических ошибок. Если у вас проблема в неправильной генерации ответа или в устаревшей бизнес-логике, ETag не спасёт. Это просто механизм проверки версии. Если хотите действительно ускорить динамику, иногда стоит смотреть на Redis, OPcache и даже на структуру SQL-запросов. Я об этом отдельно писал в статье про Redis для WordPress, Битрикс и Laravel.
Last-Modified: когда помогает и как не ошибиться
Last-Modified — старый, но всё ещё рабочий механизм. Он проще, чем ETag, и его легко объяснить: ресурс изменился тогда-то, и если с прошлого визита дата не поменялась, браузер может не скачивать его заново. Для многих серверных конфигураций это вполне достаточно.
Я бы использовал Last-Modified там, где дата изменения реально отражает свежесть контента. Например, для новостных страниц, карточек товаров, страниц блога или выгрузок. Но если дата меняется не по делу, Last-Modified начинает врать. А врать кэшу нельзя. Это плохая идея.
Типичный пример: CMS перезаписывает файл шаблона при каждой сборке или при каждом сохранении настроек, хотя визуально ничего не изменилось. Тогда Last-Modified сбрасывается постоянно, и браузер перестаёт эффективно кэшировать. На практике это встречается и в WordPress, и в Bitrix, и в самописных проектах на Laravel.
Иногда я комбинирую Last-Modified с Cache-Control: no-cache. Это не означает “не кэшировать”, как думают некоторые. Это означает: можно хранить, но перед использованием нужно проверить актуальность. Для HTML это очень полезно, если сайт обновляется часто, а часть страниц должна быстро получать 304 вместо полного ответа.
В Apache через .htaccess можно задать базовое поведение примерно так:
# Apache .htaccess: базовые заголовки для кэширования
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
ExpiresByType image/avif "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|png|jpg|jpeg|gif|svg|webp|avif|woff2)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>
Но я бы не делал ставку только на .htaccess в тяжёлом проекте. На хостинге Apache такое ещё терпимо, а вот на Nginx или в связке Nginx + Apache логичнее настраивать всё на уровне веб-сервера. Если не уверены, иногда лучше заказать расчёт стоимости доработок сайта, чем потом тратить деньги на исправление последствий.
Лучшие подходы для Nginx, Apache, WordPress, Bitrix и Laravel
На практике я разделяю настройку по стеку. В Nginx проще и чище управлять заголовками на уровне location. В Apache чаще приходится жить с .htaccess и ограничениями shared-хостинга. В WordPress обычно важно не сломать плагины кеширования. В Битрикс — не задеть встроенный композит и особенности управляемого кеша. В Laravel — аккуратно отстроить ответы контроллеров и middleware.
Для WordPress я обычно начинаю с проверки плагинов вроде LiteSpeed Cache, WP Rocket или FlyingPress. Но если сайт уже работает на сервере с Nginx, надо понять, кто именно ставит заголовки — плагин, nginx-конфиг или CDN. Иначе получится конфликт. А если проект на Битрикс, то кэширование лучше смотреть в связке с настройкой кеширования в Битрикс и композитным режимом. Там легко сделать хуже, чем было.
Для Laravel я люблю middleware или глобальные response headers. Это удобно, когда нужно централизованно управлять Cache-Control и условными запросами. Если сайт большой, часто достаточно одного слоя, чтобы обслуживать статику отдельно, а JSON и HTML — по своим правилам. И да, если у вас ещё не обновлён PHP, я бы смотрел в сторону 8.2 или 8.3. На PHP 8.1 тоже всё работает, но 8.3 уже приятнее по стабильности и скорости в типичных задачах.
Иногда помогает простая таблица подходов:
- CSS/JS/изображения: long cache, immutable, versioning в имени файла.
- HTML публичных страниц: short cache или no-cache + ETag/Last-Modified.
- Личный кабинет и корзина: private, no-store, защита от кэширования.
- API: зависит от частоты обновления, часто ETag или короткий max-age.
- Админка: почти всегда без агрессивного кэша.
У одного клиента на WordPress с WooCommerce проблема была в том, что один и тот же набор заголовков раздавался и на storefront, и на /cart/, и на /checkout/. Это классика жанра. После разделения правил через Nginx и отключения кэша для служебных страниц количество жалоб на “залипшую корзину” исчезло почти полностью. И да, это напрямую влияет не только на UX, но и на конверсию.
Как проверить, что всё работает правильно
Проверять нужно не “на глаз”, а через инструменты. Я обычно использую DevTools в Chrome, curl, иногда WebPageTest и Lighthouse. Важно смотреть не только сам заголовок, но и статус ответа: 200 или 304. Если вы обновили страницу, а ресурс получил 304 Not Modified, значит условный запрос работает. Если каждый раз 200 — что-то не так.
Базовая проверка через curl выглядит очень просто. Сначала смотрим заголовки ответа, потом делаем повторный запрос с условной проверкой. Это полезно и на проде, и на staging. Кстати, staging-среда для таких вещей — отличная идея. Я об этом писал в материале про staging-сайт для безопасной проверки обновлений.
# Первый запрос: смотрим заголовки
curl -I https://example.com/assets/app.css
# Повторный запрос с If-None-Match
curl -I https://example.com/assets/app.css \
-H 'If-None-Match: "abc123"'
# Повторный запрос с If-Modified-Since
curl -I https://example.com/assets/app.css \
-H 'If-Modified-Since: Wed, 05 Feb 2026 10:00:00 GMT'
В Chrome DevTools я обычно открываю вкладку Network и смотрю колонку Size. Если там написано from disk cache или from memory cache — уже хорошо. Но для динамики важнее увидеть 304. Для статики, наоборот, приятно видеть, что файл взят из кэша без лишней загрузки. На больших проектах это даёт заметную экономию по времени загрузки и по трафику. Иногда на PageSpeed можно выиграть 5–15 пунктов, а в Lighthouse 2026 — ещё больше, если параллельно починить шрифты, изображения и лишние запросы.
Если хотите глубже проверить общую скорость и узкие места, я бы ещё посмотрел на Core Web Vitals и Google PageSpeed Insights 2026. Потому что заголовки кэша — это не магия. Они помогают, но не заменяют нормальную фронтенд- и серверную оптимизацию.
Типичные ошибки и как их избежать
Первая ошибка — ставить одинаковые правила на всё. Вторая — забывать про CDN. Третья — не учитывать авторизацию и cookie. Четвёртая — кэшировать HTML так же агрессивно, как картинки. Пятая — включать ETag на кластере и потом ловить странные 200 вместо 304. И это ещё не полный список.
Я особенно не люблю ситуацию, когда разработчик копирует “универсальный конфиг” из интернета без понимания условий. На PHP 8.2 сайт может работать нормально, а на 8.3 вылезут тонкие различия в middleware, заголовках или поведении кеша. В связке с MySQL 8.0 и Redis это уже может дать совсем другой профиль нагрузки, и тогда универсальные советы не работают.
Ещё один частый косяк — не делать инвалидацию кэша при обновлении файлов. Если у вас не versioned filenames, а просто style.css, то после обновления нужно либо сбрасывать кэш на CDN, либо менять query string, либо пересобирать имя файла. Иначе пользователи будут жить в прошлом. Для статичных ресурсов я однозначно советую хэш в имени. Это практичнее, чем надеяться на добрую волю браузера.
Если сайт уже “поплыл” после нескольких обновлений, я бы не ограничивался только заголовками. Тут часто всплывает технический долг, сломанные редиректы, проблемы с SSL, mixed content, старые плагины и кривые кеширующие модули. Иногда проще и дешевле сделать нормальную доработку сайта под задачу, чем по кускам лечить последствия десятка старых решений.
Моя практическая схема для 2026 года
Если мне нужно быстро и без лишней экзотики настроить кэширование на новом проекте, я обычно иду по такой схеме. Для статики — Cache-Control на год, immutable, versioning через хэш. Для HTML — короткий кэш или no-cache с ETag. Для API — по ситуации, но часто short cache плюс условные запросы. Для приватных страниц — no-store или private, чтобы ничего лишнего не уезжало в shared cache.
Если сайт на WordPress, я отдельно проверяю плагины кеширования, CDN и правила сервера. Если Битрикс — композитный режим, managed cache и настройки на стороне nginx. Если Laravel — middleware и response headers, а заодно очередь задач, Redis и OPcache. И, конечно, смотрю на то, как всё это влияет на реальную производительность, а не на “красивую” цифру в одном отчёте.
В 2026 году моя рекомендация простая: не пытайтесь настроить Cache-Control, ETag и Last-Modified “на авось”. Сначала определите тип ресурса, потом выберите механизм, а уже затем проверяйте результат через curl и DevTools. Если всё сделано грамотно, сайт начинает отвечать быстрее, сервер разгружается, а пользователи меньше видят лишних перезагрузок и устаревшего контента.
И если нужен кто-то, кто не просто “поставит заголовки”, а аккуратно разложит всю логику по серверу, CMS и CDN, я бы не откладывал это в долгий ящик. На деле такие вещи хорошо решаются в рамках нормальной настройки силами специалиста, особенно когда проект уже растёт и любая мелкая ошибка стоит денег.
Хотите настроить кэш заголовки без ошибок?
Поможем подобрать и внедрить Cache-Control, ETag и Last-Modified под ваш сайт, чтобы ускорить загрузку и обновления.
