// Engineering Log

HTTPS и веб-сервер на практике: Часть 4 — Кеширование HTTP и CDN: Cache-Control, ETag, proxy_cache и сброс кеша

Опубликовано 06.11.2026 · Автор:

// Быстрый маршрут

Эта статья относится к теме Серверы и инфраструктура.

Кеширование — самый дешёвый способ ускорить сайт и снизить нагрузку на сервер: ответ, который не нужно заново генерировать и передавать, приходит мгновенно. Но у кеша есть обратная сторона — пользователи видят старые стили после выкладки, CDN раздаёт одному посетителю личную страницу другого, а изменения на сайте «не применяются». Почти все такие проблемы решаются правильными заголовками Cache-Control и пониманием того, где именно хранится копия. Краткий обзор заголовков кеширования — в статье «Заголовки HTTP», здесь — как ими пользоваться.

Где хранятся копии

Между пользователем и приложением обычно несколько кешей:

  1. Кеш браузера — личный, только для одного пользователя.
  2. CDN (Cloudflare, облачные и российские CDN) — общий кеш на серверах по всему миру; одна копия раздаётся всем посетителям.
  3. Обратный прокси — Nginx с proxy_cache, Varnish — общий кеш рядом с приложением.
  4. Кеш внутри приложения — Redis, memcached, кеш страниц в CMS. К HTTP-заголовкам отношения не имеет.

Правила для первых трёх задаёт сервер в заголовках ответа. Общие кеши (CDN, прокси) по умолчанию осторожнее браузера, и у CDN есть собственные настройки, которые могут переопределять заголовки.

Как кеш решает, что делать

Копия в кеше бывает свежей или устаревшей. Свежую кеш отдаёт, не спрашивая сервер. Устаревшую сначала проверяет — условным запросом с If-None-Match (по ETag) или If-Modified-Since (по Last-Modified). Если ресурс не изменился, сервер отвечает коротким 304 Not Modified без тела, и кеш продлевает копию.

Сколько копия остаётся свежей, задаёт Cache-Control: max-age в секундах. Если ни Cache-Control, ни Expires нет, кеши вправе сами оценить срок — обычно 10 % от времени с момента Last-Modified. Файл, изменённый год назад, браузер может считать свежим больше месяца. Поэтому явные заголовки нужны всегда.

Директивы Cache-Control

ДирективаСмысл
max-age=Nсвежая N секунд
s-maxage=Nто же, но только для общих кешей; приоритетнее max-age
no-cacheхранить можно, но перед каждым использованием проверять у сервера
no-storeне хранить нигде
privateтолько браузер, общим кешам нельзя
publicможно хранить в общих кешах, даже если запрос был с Authorization
must-revalidateустаревшую копию нельзя отдавать без проверки, даже если сервер недоступен
immutableресурс не изменится — не проверять даже при обновлении страницы
stale-while-revalidate=NN секунд после устаревания отдавать старую копию, обновляя её в фоне
stale-if-error=NN секунд отдавать старую копию, если сервер отвечает ошибкой

Для CDN есть отдельный заголовок CDN-Cache-Control (RFC 9213): он действует только на CDN и не виден браузеру. Так можно держать страницу в CDN час, а в браузере — не кешировать.

Готовые стратегии

Статика с хешем в имени (app.3f9a1c.js, style.8b2e.css — так собирают Vite, webpack, Hugo Pipes): при изменении файла меняется имя, поэтому старую копию можно хранить вечно.

Cache-Control: public, max-age=31536000, immutable

HTML-страницы: ссылки на новые версии статики лежат в HTML, поэтому HTML должен обновляться сразу.

Cache-Control: no-cache
ETag: "5f2a-18c4"

Браузер хранит страницу, но каждый раз проверяет: если ничего не изменилось — 304 без тела.

Статика без хеша (картинки из CMS, favicon.ico, robots.txt): короткий срок и проверка.

Cache-Control: public, max-age=3600, stale-while-revalidate=86400

Личные страницы и ответы API с данными пользователя:

Cache-Control: private, no-store

Публичные данные API, которые меняются часто (курсы, остатки):

Cache-Control: public, max-age=10, stale-while-revalidate=30

Настройка в Nginx

Заголовки для статики и HTML:

nginx
location ~* \.(?:js|css|woff2)$ {
    # только для файлов с хешем в имени
    add_header Cache-Control "public, max-age=31536000, immutable" always;
}

location ~* \.(?:png|jpe?g|webp|avif|svg|ico)$ {
    add_header Cache-Control "public, max-age=86400" always;
}

location / {
    add_header Cache-Control "no-cache" always;
    try_files $uri $uri/ /index.html;
}

Nginx сам отдаёт ETag и Last-Modified для статических файлов и отвечает 304 на условные запросы. Для ответов приложения ETag должно ставить приложение.

Кеш в Nginx: proxy_cache

Nginx может сам хранить ответы приложения и отдавать их без обращения к нему:

nginx
proxy_cache_path /var/cache/nginx/app levels=1:2 keys_zone=app:50m
                 max_size=2g inactive=7d use_temp_path=off;

server {
    location / {
        proxy_pass http://127.0.0.1:8080;

        proxy_cache app;
        proxy_cache_key "$scheme$host$request_uri";
        proxy_cache_valid 200 301 10m;
        proxy_cache_valid 404 1m;

        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_lock on;

        # не кешировать для вошедших пользователей
        proxy_cache_bypass $cookie_sessionid;
        proxy_no_cache     $cookie_sessionid;

        add_header X-Cache-Status $upstream_cache_status always;
    }
}
  • proxy_cache_use_stale — при ошибке приложения отдавать устаревшую копию: сайт остаётся доступным, пока приложение перезапускается;
  • proxy_cache_background_update и updating — обновлять устаревшую копию в фоне, отдавая старую (аналог stale-while-revalidate);
  • proxy_cache_lock — при промахе к приложению идёт один запрос, остальные ждут его результата. Защищает от лавины запросов, когда популярная страница выпала из кеша;
  • X-Cache-Status покажет HIT, MISS, EXPIRED, STALE, UPDATING или BYPASS.

Nginx уважает заголовки приложения: не кеширует ответы с Set-Cookie, с Cache-Control: private, no-store, no-cache, а срок из Cache-Control: max-age ставит выше, чем proxy_cache_valid.

Микрокеширование — кеш на одну секунду (proxy_cache_valid 200 1s) для публичных страниц под нагрузкой: при тысяче запросов в секунду к приложению уходит один, а данные устаревают не больше чем на секунду.

CDN

Что важно знать о CDN:

  • CDN кеширует по своим правилам. Cloudflare по умолчанию кеширует только статику по расширению файла (.js, .css, картинки, шрифты), но не HTML. Чтобы кешировать страницы, нужно правило кеширования (Cache Rules). Другие CDN могут, наоборот, кешировать всё, что разрешено заголовками;
  • ключ кеша — обычно хост, путь и строка запроса. Параметры utm_source, fbclid, yclid создают тысячи копий одной страницы — в настройках CDN их стоит исключать из ключа;
  • cookie. Если ответ зависит от cookie, а CDN кеширует его по адресу, один посетитель получит страницу другого. Персональные ответы — только private или обход кеша по наличию cookie сессии;
  • статус кеша виден в заголовках: у Cloudflare — CF-Cache-Status (HIT, MISS, EXPIRED, DYNAMIC — не кешируется по правилам, BYPASS — обход по заголовкам ответа), у других — X-Cache, а заголовок Age показывает возраст копии.

Проверка:

bash
curl -sI https://example.ru/assets/app.3f9a1c.js | grep -iE 'cache-control|cf-cache-status|x-cache|age|etag'

Повторите запрос: при правильной настройке во второй раз будет HIT и ненулевой Age.

Сброс кеша

  • Имена файлов с хешем — основной способ для статики: сбрасывать ничего не нужно, новая версия — новый адрес.
  • Параметр версии (style.css?v=42) — работает, если CDN учитывает строку запроса в ключе; хуже хеша, потому что версию меняют вручную.
  • Purge в CDN — через панель или API, по адресам или целиком. После выкладки полезно сбрасывать HTML; сброс «всего» после каждой выкладки лишает смысла кеш статики.
  • Nginx proxy_cache — удалить каталог кеша или отдельные файлы; в бесплатной версии Nginx нет команды сброса по адресу, её дают сторонние модули или Nginx Plus.
  • Кеш браузера сбросить с сервера нельзя. Если вы отдали файл с max-age=31536000 без хеша в имени, пользователи будут видеть старую версию до истечения срока или очистки кеша. Заголовок ответа Clear-Site-Data: "cache" очищает кеш сайта в браузере, но только когда браузер получит ответ с ним — а страницу, которая сама лежит в кеше, браузер у сервера не запрашивает. Поэтому длинный срок — только для файлов с хешем.

Типичные ошибки

  • Нет Cache-Control вообще — браузеры кешируют по эвристике, и после выкладки часть пользователей видит старую вёрстку.
  • max-age на год для style.css без хеша в имени — изменения не доходят до вернувшихся посетителей.
  • HTML кешируется надолго — страница ссылается на удалённые при выкладке файлы старой сборки, сайт «ломается» у части пользователей.
  • Персональная страница без private за CDN с кешированием HTML — утечка данных между пользователями.
  • no-cache вместо no-store там, где данные не должны сохраняться на диске вовсе.
  • Сброс всего кеша CDN при каждой выкладке — после неё все запросы идут на сервер одновременно.
  • Отладка «почему не обновилось» с включённым кешем браузера — проверяйте curl’ом или с Disable cache в DevTools.

// Похожая задача

Если у вас похожая ситуация

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

Тема статьи

Серверы и инфраструктура

VPS, Linux, веб-стек, миграции, хостинг, базы данных и базовая эксплуатация.

Часто с этим приходят

  • Перенести сайт или сервис на новый сервер
  • Настроить Linux, Nginx, базу данных и бэкапы
  • Разобраться, почему всё работает нестабильно

// Следующий шаг

Если вам нужна не только статья, а помощь по этой теме, удобнее сразу перейти в услугу. Главная и подборка материалов остаются рядом.

Открыть услуги

// Reviews

Отзывы по теме

Спасибо Михаилу за отзывчивость. Созвонились, объяснил как сделать самому. Обращаюсь уже второй раз, все супер и оперативно.

Спасибо Михаилу за отзывчивость. Созвонились, объяснил как сделать самому. Обращаюсь уже второй раз, все супер и оперативно.

kireevk

kireevk

Консультация по nginx proxy manager и portainer

25.02.2025 · ★ 5/5

Освоившийся покупатель
apande

apande

Настройка nginx и opencart

07.09.2024 · ★ 5/5

Очень мощный покупатель
kireevk

kireevk

Диагностика Nginx Proxy Manager в docker контейнере и решение проблемы

15.03.2024 · ★ 5/5

Освоившийся покупатель

// Contact

Нужна помощь?

Свяжись со мной и я помогу решить проблему

Написать в Telegram

Отвечаю в течение рабочего дня (03:00–13:00 GMT)

Или оставьте заявку здесь:

Подтвердите, что вы не бот.

Написать и получить быстрый ответ