ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

Nginx: как перестать отдавать старый контент после деплоя (cache, open_file_cache, CDN purge)

После деплоя часть пользователей видит старые CSS/JS или HTML: виноваты open_file_cache, proxy/fastcgi cache, заголовки Cache-Control и edge-кеш CDN. Разбираем диагностику и даём рабочие рецепты без простоя.
Nginx: как перестать отдавать старый контент после деплоя (cache, open_file_cache, CDN purge)

Симптом: «задеплоили, а Nginx отдаёт старое»

Классическая ситуация: вы выкатили новую версию, перезагрузили сервисы «как обычно», но часть пользователей продолжает видеть старые стили, старый JS-бандл или даже старую HTML-страницу. На сервере файлы новые, в логах всё «ок», а снаружи — stale content.

Почти всегда это не одна причина, а сумма нескольких уровней кеширования:

  • Nginx open_file_cache (кеш дескрипторов, метаданных и ошибок по файлам);
  • кеш Nginx на уровне proxy_cache / fastcgi_cache;
  • браузерный кеш, которым управляют cache-control, etag, last-modified;
  • CDN/edge-кеш и необходимость purge;
  • особенности деплоя без простоя: атомарные релизы, симлинки, graceful reload.

Ниже — практичный разбор: как быстро локализовать источник «залипания» и какие настройки реально лечат проблему, а не маскируют её.

Быстрая диагностика: где именно «залипло»

1) Проверяем, что реально отдаётся по сети

Начните с команды, которая не зависит от кеша браузера: проверьте заголовки и косвенные признаки кеширования.

curl -I https://example.com/assets/app.css

Смотрите на cache-control, etag, last-modified, а также на заголовки CDN (часто это age, иногда x-cache или аналоги). Если age есть и растёт — вероятно, ответ отдаётся с edge. Если age нет, но у вас есть собственные признаки кеша Nginx, значит проблема может быть в proxy_cache/fastcgi_cache.

2) Отличаем «браузер держит» от «сервер отдаёт»

Если curl стабильно показывает новую версию, а в браузере «старьё» — почти всегда виноваты политики кеширования или отсутствие версионирования URL ассетов. В DevTools проверяйте:

  • обслуживается ли запрос из memory/disk cache;
  • уходит ли условный запрос If-None-Match/If-Modified-Since;
  • какой статус: 200 или 304.

3) Временно добавляем «маркер релиза» в ответ

Для инцидентов и разбора «половина нод обновилась, половина нет» удобно на короткое время добавить заголовок версии релиза.

add_header X-Release "2026-01-23.1" always;

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

open_file_cache: почему «файл новый, а Nginx как будто не видит»

Модуль open_file_cache кеширует результаты open()/stat() и ошибки (например, 404) на заданное время. Это полезно под нагрузкой, но при деплое может проявляться как «сервер видит старое».

  • Nginx продолжает использовать закешированные метаданные (размер/mtime);
  • при атомарных релизах через симлинк current может удерживать открытый старый inode;
  • может кешировать «нет такого файла», и после выкладки файл некоторое время всё ещё считается отсутствующим.

Критичный момент: симлинки и inode

Если деплой — это переключение симлинка на новую директорию релиза, путь остаётся прежним, а inode у целевого файла меняется. В зависимости от параметров open_file_cache Nginx может продолжать обслуживать запросы «по старой реальности».

nginx -s reload перечитывает конфиг и корректно делает graceful reload, но сам по себе не обязан мгновенно «обнулить» файловые метаданные, если вы дали им жить слишком долго.

Рецепт 1: делаем open_file_cache дружелюбным к деплою

Если open_file_cache вам нужен, держите окно потенциального устаревания коротким и включайте регулярную проверку.

open_file_cache max=10000 inactive=10s;
open_file_cache_valid 5s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

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

Рецепт 2: не кешируем «горячие» пути

Если у вас часто меняется HTML (SSR, шаблоны, index.html для SPA), иногда проще отключить или «сократить» влияние файлового кеша точечно, а open_file_cache оставить там, где статика реально неизменяемая (шрифты, картинки).

Проверьте контекст применения директив в вашей версии Nginx: часть параметров можно задавать на уровне http/server, а логику разделения лучше делать по разным server или отдельным путям хранения/выдачи, чтобы деплой HTML не конкурировал с кешированием тяжёлой статики.

Рецепт 3: деплой без перезаписи «по месту»

Самый надёжный вариант: не менять файлы в директории, из которой Nginx прямо сейчас обслуживает трафик. Выкладывайте релиз в новый каталог и переключайте симлинк/маршрутизацию. Тогда вы управляете обновлением через версионирование URL и заголовки, а не через «авось reload всё сбросит».

Проверка HTTP-заголовков и признаков кеширования через curl для диагностики stale content

Cache-Control, ETag, Last-Modified: как сделать браузер союзником

Частая причина «nginx stale content» на деле — браузер. Вы выдали ассету долгий cache-control, но URL не меняете. Браузер действует правильно: «мне разрешили хранить неделю — храню неделю».

Правило №1: неизменяемым ассетам — неизменяемые имена

Если вы собираете фронтенд (Webpack/Vite/Rollup), включайте хеши в именах файлов: app.3f2a1c.css, main.9b77e1.js. Тогда можно безопасно выдавать долгий кеш: новый деплой создаёт новые URL.

  • ассеты с хешами: cache-control: public, max-age=31536000, immutable;
  • HTML (точки входа): cache-control: no-cache или короткий max-age, чтобы браузер делал revalidate;
  • не смешивайте подходы: «долгий кеш» только там, где URL гарантированно меняется.

Базовый пример для статики и точки входа

location ^~ /assets/ {
    expires 365d;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

location = /index.html {
    expires -1;
    add_header Cache-Control "no-cache";
}

Так вы перестаёте воевать с кешем клиента: новая версия — новый URL. Старая может жить сколько угодно, но её никто не запросит.

ETag и Last-Modified: где помогают, а где нет

etag и last-modified экономят трафик через 304, но не спасают, если вы дали файлу большой max-age и не меняете URL. При длинном max-age браузер может вообще не делать запрос на сервер.

  • для HTML/API чаще всего правильно: cache-control: no-cache (кешировать можно, но всегда перепроверять);
  • для хешированной статики достаточно immutable, и вы меньше зависите от нюансов etag/last-modified;
  • если несколько нод и разная ФС: следите за временем (NTP), иначе сравнение по last-modified может вести себя неожиданно.

Если вы активно оптимизируете отдачу статики и формат изображений, пригодится разбор про маппинг и кеширование альтернативных форматов: WebP/AVIF в Nginx через map и правильный кеш.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Nginx proxy_cache/fastcgi_cache: почему кэш сервера переживает деплой

Если у вас включён proxy_cache или fastcgi_cache, то «устаревший контент» может быть не про файлы. Кеш хранит HTTP-ответы бэкенда и будет отдавать их до истечения TTL или до явной инвалидации.

Проверяем, участвует ли кеш

Добавьте диагностический заголовок (на время или постоянно в ограниченном виде):

add_header X-Cache-Status $upstream_cache_status always;

Типичные значения: HIT, MISS, BYPASS, EXPIRED. Если после деплоя на HTML вы видите HIT там, где ожидаете изменения, причина найдена.

Стратегии, которые реально работают

  • Версионирование ключа кеша: добавляете «версию релиза» в proxy_cache_key. После деплоя ключи становятся новыми автоматически.
  • Явная инвалидация: точечный purge/очистка по ключам или управляемая замена области кеша.
  • Не кешировать то, что меняется и персонализируется: HTML, ответы с cookie/authorization, личные кабинеты — иначе stale станет нормой.

Вариант 1: «версия релиза» в ключе кеша

Если вы меняете версию релиза при деплое (например, через include-файл или шаблон конфигурации), встройте её в ключ.

set $cache_version "2026-01-23.1";
proxy_cache_key "$scheme$request_method$host$request_uri|$cache_version";

После смены $cache_version Nginx начнёт заполнять новый слой кеша без массового purge. Старый слой умрёт по TTL.

Вариант 2: очистка кеша как часть деплоя (осторожно)

Если кеш небольшой и проще «снести и прогреть», делайте это процедурно, понимая риски по нагрузке:

sudo systemctl reload nginx
sudo rm -rf /var/cache/nginx/proxy_cache/*

На нагруженных проектах лучше версионирование ключей или точечная инвалидация, иначе каждый релиз превращается в «холодный старт» для бэкенда.

CDN purge: когда «старое» сидит не у вас

Если перед Nginx стоит CDN, то даже правильные заголовки и чистый Nginx-кеш не гарантируют мгновенное обновление. Edge может продолжать отдавать старое до истечения TTL или до purge.

Типовые причины stale на CDN

  • слишком длинный cache-control для HTML;
  • нет версионирования URL ассетов;
  • включён «cache everything» без исключений для HTML/API;
  • несколько origin-серверов, часть отвечает старым релизом, а CDN закешировал «неудачный» вариант;
  • purge сделан не по тем путям.

Практика: разделяйте правила для HTML и ассетов

Самый безопасный паттерн:

  • CDN кеширует только статические ассеты с хешами (долго);
  • HTML либо не кешируется на CDN, либо кешируется очень коротко с валидацией;
  • точки входа (например, /, /index.html) имеют минимальный TTL.

Тогда purge требуется редко: деплой меняет ссылки на ассеты, CDN подтянет новые по новым URL.

Если purge всё же нужен: делайте его точечно

Чистите конкретные пути (HTML, manifest, критичные JSON), а не «purge all». Иначе вы сами создаёте холодный старт на каждом релизе.

Схема CDN edge-кеша, origin-сервера и точечной инвалидации (purge) после релиза

Deploy without downtime: как деплоить так, чтобы кеши не мешали

«Деплой без простоя» — это не только отсутствие 502 при перезапуске. Это ещё и предсказуемость выдачи контента сразу после релиза.

Опорный набор практик

  • Атомарный релиз: выкладка в новый каталог и переключение симлинка.
  • Версионирование ассетов: хеши в именах и долгий кеш с immutable.
  • HTML всегда перепроверяем: cache-control: no-cache или небольшой max-age с revalidate.
  • Управляемая инвалидация: версия в cache key или точечный purge, а не «надеемся на reload».
  • Наблюдаемость: заголовок версии релиза, X-Cache-Status, метрики hit ratio.

Проверочный чек-лист после деплоя

  1. Проверьте главную HTML: нет ли cache-control: max-age=... на часы/дни.
  2. Проверьте один CSS/JS с хешем: есть ли immutable и долгий TTL.
  3. Убедитесь, что HTML ссылается на новые хешированные файлы.
  4. Если есть CDN: посмотрите age и убедитесь, что HTML не застрял на edge.
  5. Если есть Nginx cache: проверьте $upstream_cache_status на HTML.
  6. Если есть open_file_cache: убедитесь, что open_file_cache_valid не измеряется минутами.
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Типовые анти-паттерны, которые почти гарантируют stale content

  • Одинаковый URL для app.js при долгом cache-control.
  • Кеширование HTML «как статики» на CDN на часы/дни.
  • open_file_cache_valid в десятки секунд/минут при частых релизах и симлинках.
  • Смешанный парк нод: часть обновилась, часть нет, а балансировщик/CDN кешируют ответы без привязки к версии.
  • «Purge all» на каждом деплое вместо версионирования URL или ключей.

Если вам нужно аккуратно управлять TTL и доступом к кешируемым ссылкам (например, для приватной статики), посмотрите материал про TTL в ссылках: Nginx secure_link, TTL и кеширование.

Минимальный рабочий набор настроек (как отправная точка)

Это не «серебряная пуля», а базовый ориентир, который обычно сильно снижает проблемы со stale.

# Статика с хешами
location ^~ /assets/ {
    expires 365d;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

# HTML: всегда перепроверять
location = / {
    expires -1;
    add_header Cache-Control "no-cache";
}

location = /index.html {
    expires -1;
    add_header Cache-Control "no-cache";
}

# open_file_cache: короткое окно устаревания
open_file_cache max=10000 inactive=10s;
open_file_cache_valid 5s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

Вывод

Чтобы Nginx перестал отдавать stale-контент после деплоя, думайте слоями: файловый кеш (open_file_cache), серверный HTTP-кеш (proxy_cache/fastcgi_cache), браузерный кеш через cache-control, валидация через etag/last-modified и edge-кеш CDN с процедурой purge. Самый устойчивый путь — версионировать ассеты и отделять кеширование HTML от кеширования статики: тогда деплой без простоя становится предсказуемым, а кеши начинают работать на вас.

Если проект растёт и хочется изолировать окружения, держать стабильные конфиги Nginx и контролировать кеши на уровне инфраструктуры, удобнее делать это на VDS, где вы управляете всей цепочкой доставки и обновлений.

Поделиться статьей

Вам будет интересно

Loki 429: too many outstanding requests при ingestion — причины и пошаговое исправление OpenAI Статья написана AI (GPT 5)

Loki 429: too many outstanding requests при ingestion — причины и пошаговое исправление

Loki 429 too many outstanding requests (ingestion) означает, что контур приёма логов не успевает: упёрлись в лимиты, взорвали labe ...
HTTPS timeout на мобильных: MTU/PMTUD, blackhole MTU и MSS clamping в Linux OpenAI Статья написана AI (GPT 5)

HTTPS timeout на мобильных: MTU/PMTUD, blackhole MTU и MSS clamping в Linux

Если HTTPS работает с ПК, но в LTE/5G «висит» или таймаутится, часто виноват MTU/PMTUD: крупные пакеты теряются, ICMP блокируют, п ...
MySQL ошибки 1205 и 1213: как диагностировать lock wait timeout и deadlock в InnoDB OpenAI Статья написана AI (GPT 5)

MySQL ошибки 1205 и 1213: как диагностировать lock wait timeout и deadlock в InnoDB

Ошибки MySQL 1205 (Lock wait timeout) и 1213 (Deadlock) почти всегда связаны с конкуренцией транзакций InnoDB. Разберём быструю ди ...