Кеш-стратегия для динамического сайта в Nginx: разбор
Кеширование динамических страниц в Nginx ускоряет ответы, но легко отдаёт пользователю чужую сессию или устаревшие данные. Разбираем путь запроса, ключ кеша, блокировку прогрева и границы безопасного применения.

Почему динамический сайт начинает тормозить именно на кешировании
При росте нагрузки Nginx может снять значительную часть повторных запросов с приложения: первый запрос получает ответ от upstream, а следующие читают готовый объект с диска. Но ошибка в ключе кеша или условии обхода превращает ускорение в утечку персональных данных, бесконечную раздачу устаревшей страницы или лавину запросов к приложению после истечения TTL.
Ниже — разбор для связки Nginx 1.25.x и обычного HTTP-upstream. Речь идёт о кешировании полных ответов через модуль ngx_http_proxy_module. FastCGI-кеш для PHP работает по похожей логике, но использует другие директивы и заслуживает отдельной настройки.
Кеш — это не «ускорить всё». Это правило, по которому Nginx решает, какой ответ можно повторно выдать другому запросу, а какой нужно заново получить у приложения.
Термин. TTL (time to live) — время, в течение которого объект считается пригодным для выдачи без нового обращения к upstream.
Что происходит с запросом внутри Nginx
Для кешируемого GET-запроса цепочка выглядит так:
- Nginx строит ключ кеша из схемы, хоста, URI и, при необходимости, других признаков запроса.
- По ключу он проверяет метаданные в общей памяти и объект на диске.
- При попадании в кеш Nginx возвращает сохранённый статус, заголовки и тело, не обращаясь к приложению.
- При промахе Nginx отправляет запрос upstream, получает ответ и решает, можно ли его сохранить.
- Ответ записывается во временный файл, затем перемещается в кеш-каталог. Остальные воркеры могут использовать объект после завершения записи.
В общей памяти хранится не весь HTML, а зона ключей и метаданных. Само тело ответа лежит на диске. Поэтому увеличение keys_zone не превращает кеш в оперативную память и не заменяет настройку max_size.
Ключ важнее времени жизни
Ключ должен различать ответы, которые действительно отличаются. Для публичной страницы обычно достаточно схемы, хоста и URI. Параметры запроса нужно учитывать, если они меняют содержание: например, ?page=2 или ?sort=price.
Не добавляйте в ключ случайные заголовки и все cookies подряд. Это резко уменьшает долю попаданий: каждый вариант становится отдельным объектом. Но и игнорировать cookie нельзя, если по ней приложение показывает авторизованный кабинет, регион или персональную цену.
Попадание, промах и устаревший объект
Статус кеша удобно выводить во временный диагностический заголовок. Значения вроде HIT, MISS, BYPASS, EXPIRED и UPDATING показывают, почему запрос не пошёл обычным путём. Такой заголовок не должен раскрывать внутреннюю схему в публичном интерфейсе без необходимости.
После истечения TTL объект становится устаревшим. Nginx может либо ждать новый ответ, либо временно отдать старый, если включена политика proxy_cache_use_stale. Это выбор между свежестью и доступностью. Для каталога товаров он может быть допустимым, для баланса счёта — нет.
Базовая схема кеша для публичных страниц
Ниже приведён минимальный каркас для Nginx 1.25.x. Блок proxy_cache_path и map размещают внутри http, а настройки location — внутри нужного server. Имя upstream здесь условное, его нужно сопоставить с вашим приложением.
http {
proxy_cache_path /var/cache/nginx/site
levels=1:2
keys_zone=site_cache:100m
max_size=5g
inactive=10m
use_temp_path=off;
map $request_method $skip_method_cache {
default 1;
GET 0;
HEAD 0;
}
map $http_cookie $skip_cookie_cache {
default 0;
~*session 1;
~*wordpress_logged_in 1;
}
upstream application {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name example.test;
location / {
proxy_pass http://application;
proxy_cache site_cache;
proxy_cache_methods GET HEAD;
proxy_cache_key "$scheme|$host|$request_uri";
proxy_cache_bypass $skip_method_cache $skip_cookie_cache;
proxy_no_cache $skip_method_cache $skip_cookie_cache;
proxy_cache_valid 200 30s;
proxy_cache_valid 301 10m;
proxy_cache_valid any 1s;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
add_header X-Cache-Status $upstream_cache_status always;
}
}
}
В этом примере HEAD преобразуется в GET по стандартному поведению модуля, поэтому метод намеренно не включён в ключ. Если вы меняете это поведение, ключ и правила для GET и HEAD нужно пересмотреть вместе.
Что делают важные директивы
proxy_cache_pathзадаёт каталог, общую зону ключей, максимальный размер и срок удаления неиспользуемых объектов.proxy_cache_validзадаёт срок по статусу ответа. Это не гарантия свежести данных, а только правило хранения объекта без нового запроса.proxy_cache_lock onне даёт множеству одинаковых промахов одновременно прогревать один ключ. Остальные запросы ждут в пределах тайм-аута.proxy_cache_use_stale updatingпозволяет выдавать старый объект, пока другой запрос получает свежий.proxy_cache_background_update onпереносит обновление устаревшего объекта в фоновую обработку, но требует согласованной политики stale.
Проверяйте конфигурацию до перезагрузки командой nginx -t. Затем выполните два одинаковых GET-запроса и посмотрите заголовок X-Cache-Status. В рабочем окружении диагностический заголовок лучше ограничить внутренними запросами или убрать после проверки.
Три режима и их цена
У динамического сайта нет одной правильной TTL. Выбор зависит от того, сколько стоит устаревший ответ и сколько запросов приложение выдерживает во время обновления.
| Режим | Где подходит | Плюс | Риск и цена |
|---|---|---|---|
| Полный page cache | Публичные статьи, категории, лендинги | Минимум запросов к приложению | Нельзя безопасно смешивать с персональными данными; нужна продуманная инвалидация |
| Микрокеш на секунды | Ленты, поиск, страницы с частыми изменениями | Сглаживает пики, не хранит данные долго | При низком TTL приложение всё равно регулярно получает пики обновления |
| Только кеширование ошибок и stale | Сервисы, где важнее доступность | Сохраняет ответ при кратком сбое upstream | Пользователь может увидеть устаревшее или неполное состояние |
Если страница содержит общую разметку и персональный блок, полный кеш ответа опасен. Варианты — отдавать персональную часть отдельным запросом, использовать кеширование на уровне приложения или отказаться от кеша этой страницы. Фрагментное кеширование в Nginx не появляется автоматически: его нужно проектировать отдельно.
Где кеш ломается первым
Cookie, авторизация и Set-Cookie
Запрос с cookie сессии должен обходить общий кеш, если cookie влияет на ответ. То же относится к Authorization и страницам личного кабинета. Ответ с Set-Cookie нельзя бездумно сохранять и раздавать другим пользователям. Отдельно проверьте ответы, где приложение ставит cookie не только на странице входа, но и на обычном GET.
Ориентироваться только на URL недостаточно. Два запроса к /profile имеют одинаковый URI, но разное содержимое для разных пользователей.
Методы и побочные эффекты
Кешируйте только безопасные для повторной выдачи ответы. POST, PUT, PATCH и DELETE не должны превращаться в источник общего кешированного результата. Даже если приложение иногда отвечает на POST статусом 200, это не делает операцию безопасной для кеша.
Vary и параметры запроса
Заголовок Vary сообщает, какие заголовки участвуют в выборе представления ответа. Если приложение меняет язык по Accept-Language, а ключ Nginx его игнорирует, пользователь может получить страницу на другом языке. Аналогичная проблема возникает с форматом ответа, кодировкой и мобильной версией.
С параметрами запроса нужна явная политика. Удаление tracking-параметров может повысить долю попаданий, но только если вы точно знаете, что они не меняют содержание. Автоматически вычищать все параметры опасно.
Прогрев после очистки
Очистка кеша перед релизом удаляет защитный слой сразу для всех URL. Если сотни запросов одновременно пойдут в приложение, получится cache stampede — лавина промахов кеша. proxy_cache_lock снижает риск для одного ключа, но не заменяет плановый прогрев популярных страниц и контроль нагрузки.
Как измерять результат, а не верить заголовку HIT
Замеряйте минимум четыре показателя до и после включения кеша:
- долю HIT, MISS, BYPASS и EXPIRED по URL и статусу;
- время ответа на уровне Nginx и время ожидания upstream;
- число запросов и долю ошибок приложения;
- объём каталога кеша, скорость его заполнения и частоту удаления объектов.
Сравнивайте одинаковые URL, одинаковый регион и одинаковый профиль нагрузки. Один удачный запрос с HIT ничего не говорит о поведении после публикации материала или истечения TTL. Для проверки персонализации используйте отдельные тестовые аккаунты и проверяйте, что ответ одного пользователя не появляется у другого.
Если метрик нет, сначала добавьте доступные поля в access log: URI, статус, время запроса, время upstream и значение $upstream_cache_status. Конкретные пороги нельзя честно назвать без размера ответа, числа воркеров, характеристик диска и нагрузки.
Что делать дальше
Начните с одной публичной страницы без авторизации. Зафиксируйте её допустимую устарелость, составьте ключ и список условий обхода. Затем проверьте второй запрос, обновление данных, ответ с ошибкой upstream и одновременный прогрев после очистки.
После этого вынесите в мониторинг долю BYPASS и MISS, а не только среднее время ответа. Если доля промахов растёт из-за cookie или лишних параметров, проблема находится в ключе и правилах обхода, а не в размере дискового кеша. Инвалидацию по событиям приложения, кеширование FastCGI и CDN лучше рассматривать отдельным слоем, не смешивая их с базовой схемой reverse proxy.
Антиблок-аналитик 115-ФЗ: Анализ выписок, поиск рисков блокировки счёта, инструкции по безопасным транзакциям.
Спросить за 41 ₽Источники: NGINX documentation: ngx_http_proxy_module, NGINX Admin Guide: Content Caching
Частые вопросы
Можно ли кешировать страницу, если на сайте есть авторизация?
Почему после включения кеша пользователь видит старые данные?
Что произойдёт, если приложение упадёт во время обновления кеша?
Зачем нужен proxy_cache_lock?
Почему X-Cache-Status показывает BYPASS, хотя URL публичный?
Можно ли использовать эту конфигурацию для FastCGI и PHP?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.