Новинка Виртуальный VDS сервер в Финляндии от 390р
Выберите продукт

Nginx proxy_cache for static: S3 origin, revalidation, cache lock

Пошагово настраиваем Nginx как edge‑кэш для статики с origin в S3: proxy_cache_path и cache key, revalidation через ETag/Last-Modified, cache lock от stampede, stale/background update и диагностика по заголовкам и логам.
Nginx proxy_cache for static: S3 origin, revalidation, cache lock

Зачем кэшировать S3 через Nginx, если S3 и так быстрый

Объектное хранилище по S3-протоколу хорошо подходит для раздачи статических файлов, но в реальном проде часто всплывают нюансы: лишняя задержка из‑за географии, рост стоимости egress при всплесках трафика, лимиты по запросам, а также эффект «стада» (cache stampede), когда после истечения TTL десятки или сотни клиентов одновременно пробивают origin.

nginx proxy_cache позволяет поставить перед S3 локальный edge‑кэш на диске: Nginx отдаёт популярную статику с вашего сервера, а в S3 ходит только при промахе, при revalidation или при прогреве. Это удобно, когда несколько сайтов/приложений используют общий bucket, или вы хотите жёстко контролировать TTL, заголовки и поведение при сбоях.

Ниже соберём практичную конфигурацию: S3 как origin, Nginx как reverse proxy со стабильным cache key, корректной revalidation по ETag/Last-Modified, защитой от stampede через cache lock и предсказуемым stale-поведением.

Архитектура и границы ответственности

Базовая схема: клиент → Nginx → S3. Nginx кэширует ответы (чаще всего 200, иногда редиректы, и в некоторых случаях часть 404), а при истечении TTL может «перепроверять» объект в S3 условными запросами (If-None-Match / If-Modified-Since).

Перед настройкой стоит договориться с самим собой о нескольких вещах:

  • Что именно кэшируем: картинки, CSS/JS, шрифты, архивы, media-сегменты. Для динамики кэш тоже возможен, но это отдельный класс задач.
  • Как инвалидируем: лучший вариант — версионирование в URL (например, app.9f3a1c.js), альтернативный — revalidation по заголовкам.
  • Как различаем варианты: по host, пути, query string, иногда по заголовкам (например, Accept-Encoding).
  • Что делаем при сбоях origin: отдаём stale или честно возвращаем ошибку.

Практическое правило: для статики проще всего жить с «immutable + versioned URLs». Тогда кэш можно держать долго, а revalidation понадобится редко. Если URL не версионирован, ставка на revalidation становится критичной.

Если вы раздаёте статику с отдельного домена, заранее проверьте, что на нём корректно настроены DNS и сертификат. Для собственных доменов удобна регистрация доменов, а для HTTPS — подходящие SSL-сертификаты.

Схема: клиент, Nginx edge-кэш и S3 origin

Базовая конфигурация proxy_cache: зона, путь, ключ

Фундамент начинается в контексте http: объявляем путь кэша, размер метаданных, лимиты и политику очистки.

proxy_cache_path /var/cache/nginx/s3 levels=1:2 keys_zone=s3cache:200m max_size=50g inactive=7d use_temp_path=off;
proxy_temp_path /var/cache/nginx/tmp;

Ключевые параметры:

  • keys_zone=s3cache:200m — память под метаданные (ключи/индекс), а не под сами файлы. Если зона мала, кэш будет чаще «забывать» объекты.
  • max_size=50g — жёсткий потолок по диску под кэш.
  • inactive=7d — если объект не запрашивали 7 дней, его можно удалить при очистке.
  • use_temp_path=off — запись сразу в итоговую структуру кэша (часто меньше лишнего I/O).

Дальше — серверный блок. Ниже пример, где origin — HTTPS endpoint S3 (или S3‑совместимого хранилища). Обратите внимание на Host, keepalive и диагностический заголовок.

server {
  listen 80;
  server_name static.example.com;

  resolver 1.1.1.1 1.0.0.1 valid=300s;
  resolver_timeout 5s;

  location / {
    proxy_http_version 1.1;
    proxy_set_header Connection "";

    proxy_set_header Host bucket-name.s3-region.amazonaws.com;

    proxy_hide_header x-amz-id-2;
    proxy_hide_header x-amz-request-id;

    proxy_cache s3cache;
    proxy_cache_key "$scheme$proxy_host$uri$is_args$args";

    proxy_cache_valid 200 206 1h;
    proxy_cache_valid 301 302 10m;
    proxy_cache_valid 404 5m;

    proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;

    add_header X-Cache-Status $upstream_cache_status always;

    proxy_pass https://bucket-name.s3-region.amazonaws.com;
  }
}

Самый важный момент здесь — proxy_cache_key. В примере ключ включает query string. Это безопасно по корректности, но может резко раздуть кэш, если в URL летят «мусорные» параметры (например, utm_*).

Query string и «размножение» объектов

Для static caching обычно верно одно из двух:

  • query string не используется — тогда убирайте $args из ключа и кэш станет гораздо компактнее;
  • query string — это версия (например, ?v=123) — тогда оставляйте.

Если хотите оставить только «разрешённые» параметры (например, только v), делайте это через map. В отдельной заметке мы разбирали похожую технику на примере картинок и форматов: как нормализовать ключ кэша через map.

FastFox VDS
Облачный VDS-сервер
Виртуальные серверы с быстрым запуском и гибкой конфигурацией от 390₽ / мес
Доступные локации
Россия Нидерланды Финляндия

Revalidation: проверка свежести по ETag и Last-Modified

Revalidation нужна, когда вы не можете (или не хотите) полностью полагаться на версионирование URL, и вам важно, чтобы Nginx после истечения TTL перепроверял объект в S3 условным запросом. В идеале origin отдаёт ETag и/или Last-Modified, а Nginx использует их для If-None-Match/If-Modified-Since.

Включается это директивой proxy_cache_revalidate. Когда TTL истёк, Nginx попытается получить 304 Not Modified вместо полной перекачки тела.

location / {
  proxy_cache s3cache;
  proxy_cache_valid 200 206 10m;
  proxy_cache_revalidate on;

  proxy_pass https://bucket-name.s3-region.amazonaws.com;
}

Три практических наблюдения:

  • 304 экономит трафик, но это всё равно запрос в S3. При высокой нагрузке доля revalidation-запросов может быть заметна по RPS.
  • ETag в S3 не всегда равен хэшу (особенно при multipart upload). Для revalidation это обычно не важно: вам нужна стабильная «версия», а не криптография.
  • Last-Modified часто достаточно, если вы обновляете объект перезаписью. Но если у вас нестандартный пайплайн (копирование с метаданными, специфические заголовки), проверьте поведение на тестовом bucket.

Cache lock: защита от stampede при истечении TTL

Stampede проявляется в момент, когда популярный объект протух: множество клиентов одновременно получают EXPIRED и начинают пробивать origin. Для S3 это означает всплеск запросов и задержек.

Решение — включить proxy_cache_lock. Тогда один запрос обновляет объект, а остальные ждут результат или получают stale (если вы так настроите).

location / {
  proxy_cache s3cache;
  proxy_cache_valid 200 206 10m;

  proxy_cache_lock on;
  proxy_cache_lock_timeout 10s;
  proxy_cache_lock_age 30s;

  proxy_cache_use_stale updating;

  add_header X-Cache-Status $upstream_cache_status always;

  proxy_pass https://bucket-name.s3-region.amazonaws.com;
}

Как это читать:

  • proxy_cache_lock — включает «замок» на ключ кэша.
  • proxy_cache_lock_timeout — сколько клиент готов ждать lock; после таймаута Nginx может пойти в origin сам (и тем самым частично вернуть stampede), поэтому подбирайте аккуратно.
  • proxy_cache_lock_age — страховка от зависшего lock (например, при проблемах с origin).

Связка proxy_cache_lock + proxy_cache_use_stale updating обычно даёт лучший UX: один запрос обновляет, остальные мгновенно получают старую версию без ожидания.

Stale и background update: как переживать сбои S3 и сеть

Если S3 endpoint на секунду «подвис» или сеть дала потери, превращать это в лавину 502/504 на клиенте чаще всего не хочется. Для этого Nginx умеет отдавать протухший кэш в заданных сценариях.

location / {
  proxy_cache s3cache;
  proxy_cache_valid 200 206 30m;

  proxy_cache_use_stale error timeout invalid_header http_500 http_502 http_503 http_504;
  proxy_cache_background_update on;

  proxy_pass https://bucket-name.s3-region.amazonaws.com;
}

proxy_cache_background_update означает: клиенту можно отдать stale сразу, а обновление выполнить в фоне. Это снижает хвосты latency и делает поведение более «CDN‑похожим».

Для статики отдача stale при ошибках почти всегда лучше, чем «честная» 502. Исключение — когда устаревшая версия критична (например, юридически значимые файлы) и обновления происходят часто без версионирования.

Range/206 и большие файлы: что учитывать

S3 часто используют для видео, архивов и образов, где клиенты активно делают Range‑запросы. Nginx умеет кэшировать ответы 206 Partial Content, но у этого есть цена: один и тот же объект может храниться «кусочками», увеличивая количество файлов в кэше и нагрузку на диск.

Если Range‑трафика много, имеет смысл отдельно протестировать:

  • нужно ли кэшировать 206 вообще или достаточно кэшировать только 200;
  • каковы реальные паттерны клиентов (плееры, докачки, распараллеливание);
  • как быстро растёт кэш по числу объектов и inode.

Тему Range и кэширования больших файлов мы подробно разбирали отдельно: как Nginx ведёт себя с Range и кэшем.

Диагностика proxy_cache: заголовок X-Cache-Status и логи с upstream-таймингами

Диагностика: как быстро понять, что кэш работает

Первое, что стоит добавить — заголовок со статусом кэша:

add_header X-Cache-Status $upstream_cache_status always;

Типовые значения $upstream_cache_status:

  • HIT — отдали из кэша.
  • MISS — сходили в origin и закэшировали.
  • BYPASS — кэш намеренно пропущен (обычно из‑за условий proxy_cache_bypass).
  • EXPIRED — объект протух и пошёл обновляться (часто в паре с revalidation).
  • UPDATING — кто-то обновляет, вы получили stale (при proxy_cache_use_stale updating).

Второй слой — логи. Удобно иметь отдельный log_format для статического прокси, чтобы видеть кэш‑статус, upstream‑статус и тайминги.

log_format s3cache '$remote_addr - $host [$time_local] "$request" $status '
                 'cache=$upstream_cache_status upstream=$upstream_status '
                 'rt=$request_time urt=$upstream_response_time '
                 'uaddr=$upstream_addr';

И применить формат в нужном server:

access_log /var/log/nginx/static-access.log s3cache;

Так вы быстро поймёте, почему вдруг всё стало MISS (ключ «гуляет»), почему слишком много EXPIRED (TTL маловат), и помогает ли revalidation (доля 304 у upstream).

Тонкости S3 origin: Host, SNI и соединения

Для S3‑совместимых endpoint’ов критично корректно задавать Host, потому что маршрутизация часто завязана на виртуальные хосты. Поэтому в примерах есть proxy_set_header Host …. Если провайдер требует path‑style (bucket в пути), меняется только способ формирования URL в proxy_pass, но принцип тот же: запрос должен выглядеть так, как ожидает S3.

Для стабильности соединений полезно:

  • использовать proxy_http_version 1.1 и proxy_set_header Connection "", чтобы keepalive работал предсказуемо;
  • выставить разумные proxy_connect_timeout и proxy_read_timeout (особенно для больших объектов);
  • задать resolver в Nginx, если origin — доменное имя и вы хотите управлять TTL резолва.
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Частые ошибки и быстрые проверки

Кэш есть, но всегда MISS

Проверьте, не меняется ли ключ: $proxy_host, $uri, $args. Частая причина — мусорные query‑параметры или разные варианты по заголовкам (например, сжатие), которые не учтены одинаково для всех запросов.

Кэш разрастается без контроля

Типовые причины: query string в ключе, кэширование 206 для крупных файлов, слишком большой inactive при большом «ассортименте» объектов. Отдельно следите за inode на файловой системе, а не только за гигабайтами.

Revalidation не даёт экономии

Если объекты часто меняются без версионирования URL, условные запросы будут часто возвращать 200 вместо 304. Смотрите в логах $upstream_status и долю EXPIRED. Иногда проще изменить процесс деплоя статики (versioned filenames), чем пытаться «умно» revalidate’ить всё.

При истечении TTL растёт latency

Почти всегда это лечится включением proxy_cache_lock и proxy_cache_use_stale updating. Это сглаживает нагрузку на origin и убирает «хвосты» задержек.

Сбалансированный пресет для статики с S3

Если нужно быстро получить предсказуемый результат, начните с такого набора (при условии, что статика не зависит от query string):

proxy_cache s3cache;
proxy_cache_key "$scheme$proxy_host$uri";
proxy_cache_valid 200 206 30m;
proxy_cache_valid 404 2m;

proxy_cache_revalidate on;
proxy_cache_lock on;
proxy_cache_lock_timeout 10s;
proxy_cache_lock_age 30s;

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

add_header X-Cache-Status $upstream_cache_status always;

Логика простая: не раздуваем кэш $args, включаем revalidation для «мягкого» обновления, добавляем lock+stale updating от stampede и background update для стабильной задержки.

Финальный чек‑лист перед продом

  1. Проверьте права и место на диске под /var/cache/nginx, настройте мониторинг заполнения и inode.
  2. Добавьте X-Cache-Status и отдельный лог‑формат хотя бы на время внедрения.
  3. Решите вопрос с query string: исключить, оставить или нормализовать через map.
  4. Включите proxy_cache_lock и proxy_cache_use_stale updating для горячих объектов.
  5. Если обновления должны быть строгими — внедрите версионирование URL на стороне сборки/деплоя.

При аккуратной настройке связка Nginx + S3 даёт поведение, близкое к CDN: быстрые HIT, мягкие обновления через revalidation и предсказуемую нагрузку на origin за счёт cache lock.

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

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

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

Как безопасно выполнить bootstrap MariaDB Galera после полного останова

После одновременной остановки всех узлов Galera обычного запуска MariaDB недостаточно. Сначала найдите сервер с последней транзакц ...
CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

CoreDNS в Kubernetes: исправляем CrashLoopBackOff, SERVFAIL и DNS-петлю

Разберём, почему CoreDNS перезапускается или отвечает SERVFAIL, как обнаружить замкнутую пересылку DNS-запросов, проверить доступн ...
Apache Kafka KRaft на VDS: отдельные controller и broker

Apache Kafka KRaft на VDS: отдельные controller и broker

Соберём новый Kafka-кластер без ZooKeeper на шести Linux VDS: три узла controller образуют отказоустойчивый metadata quorum, а три ...