Введение: зачем управлять кэшем статики в Nginx
Неправильное управление кэшем статических файлов приводит к двум проблемам: либо сервер отдаёт устаревшие изображения и стили, либо после очистки кэша бэкенд получает лавину запросов и деградирует. Для высоконагруженных проектов с большим объёмом медиаданных это означает простой и потерю пользователей. Решение - выстроить управление кэшем на трёх уровнях: кэширование метаданных файлов через open_file_cache, кэширование ответов через proxy_cache и интеграция с CDN.
Nginx предоставляет несколько механизмов кэширования, каждый из которых решает свою задачу. open_file_cache ускоряет отдачу статики за счёт хранения дескрипторов файлов и метаданных в памяти. proxy_cache сохраняет готовые ответы от бэкенда и отдаёт их без обращения к приложению. Правильная очистка этих кэшей не должна вызывать пиковых нагрузок: для этого используются stale-кэш, атомарное переименование каталогов и прогрев после инвалидации.
В этой статье разобраны рабочие конфигурации для production-сред, методы безопасной очистки и типичные ошибки, которые приводят к простоям. Все примеры проверены на практике и адаптированы под Nginx 1.24+ и 1.26+.
Основы кэширования статики в Nginx: open_file_cache и proxy_cache
Nginx работает со статикой на двух уровнях. Первый - файловый кэш операционной системы, который Nginx может частично дублировать через open_file_cache. Второй - прокси-кэш, который сохраняет HTTP-ответы целиком. Выбор механизма зависит от того, откуда Nginx берёт файлы: напрямую с диска или через бэкенд.
Настройка open_file_cache для ускорения отдачи файлов
По умолчанию Nginx при каждом запросе к статическому файлу выполняет системные вызовы open(), stat() и close(). На высоконагруженном сервере с тысячами RPS это создаёт заметную нагрузку на CPU и дисковую подсистему. Директива open_file_cache сохраняет дескрипторы файлов и метаданные в памяти, сокращая количество системных вызовов.
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;Разбор параметров:
- max - максимальное количество элементов в кэше. При превышении Nginx удаляет наименее используемые записи. Для сайта с 5000+ статических файлов ставьте 5000–10000, но следите за памятью.
- inactive - время неактивности, после которого запись удаляется. Значение 20s означает, что файл, не запрошенный за 20 секунд, выбывает из кэша.
- valid - период, в течение которого Nginx считает метаданные актуальными и не проверяет файл на диске. При 30s изменение файла может быть не замечено до 30 секунд.
- min_uses - минимальное количество обращений, после которого файл попадает в кэш. Значение 2 отсеивает случайные запросы к редко используемым файлам.
- errors - кэшировать ли ошибки открытия файлов. Включение предотвращает повторные попытки открыть несуществующий файл.
Типичная конфигурация для сайта с большим количеством изображений:
http {
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 120s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
server {
location /static/ {
root /var/www;
open_file_cache max=5000 inactive=30s;
open_file_cache_valid 60s;
}
}
}Предупреждение: при слишком большом max и длительном inactive возможна утечка памяти. Каждый элемент кэша занимает примерно 100–200 байт, но при 100000 элементов это уже 10–20 МБ. Для виртуальных серверов с ограниченной памятью проверяйте фактическое потребление через ps -o rss -p $(cat /var/run/nginx.pid).
Использование proxy_cache для кэширования ответов
proxy_cache полезен для статики, когда файлы генерируются на лету: ресайз изображений, генерация превью, обработка загрузок. Вместо повторной генерации Nginx сохраняет готовый HTTP-ответ и отдаёт его из дискового кэша.
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
location /images/ {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 30d;
proxy_cache_valid 404 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}Параметры proxy_cache_path:
- levels=1:2 - двухуровневая структура каталогов. Предотвращает проблемы с файловыми системами при хранении сотен тысяч файлов в одном каталоге.
- keys_zone - имя и размер зоны разделяемой памяти для хранения ключей и метаданных. Зона 10m вмещает примерно 80000 ключей.
- max_size - максимальный размер кэша на диске. При превышении Nginx запускает фоновый процесс удаления наименее используемых данных.
- inactive - время, после которого неиспользуемые записи удаляются из кэша.
- use_temp_path=off - запись временных файлов непосредственно в каталог кэша, что ускоряет операции и исключает двойное копирование.
Для статики, которая меняется редко, задавайте длительный срок жизни. Изображения товаров в каталоге можно кэшировать на 30 дней, а превью, которые перегенерируются при изменении исходника, - на 1 час.
Безопасная очистка кэша: стратегии и инструменты
Простое удаление файлов из каталога кэша создаёт резкий всплеск нагрузки: все последующие запросы идут к бэкенду одновременно. Для высоконагруженных проектов это равносильно DDoS-атаке на собственное приложение. Безопасная очистка предполагает два подхода: использование stale-кэша для смягчения последствий и атомарное переименование каталога для мгновенного переключения.
Использование stale-кэша для смягчения последствий очистки
Директива proxy_cache_use_stale разрешает Nginx отдавать устаревшие данные, когда бэкенд недоступен или обновление кэша завершилось ошибкой. Это критически важно в момент очистки: если кэш удалён, а бэкенд не выдерживает нагрузки, пользователи получают stale-ответы вместо ошибок 502/504.
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;Как это работает при очистке:
- Вы удаляете записи из кэша.
- Пользователь запрашивает ресурс, Nginx обращается к бэкенду.
- Бэкенд не отвечает или отвечает с ошибкой 500/502/503/504.
- Nginx проверяет, есть ли stale-версия ответа. Если есть - отдаёт её с заголовком
Warning: 110.
Для статики stale-кэш особенно полезен при обновлении большого количества изображений. Вы можете очистить кэш, не опасаясь, что пользователи увидят ошибки, пока бэкенд перегенерирует файлы.
Атомарная очистка каталога кэша
Метод атомарного переименования позволяет мгновенно переключить Nginx на новый пустой каталог кэша, а старый удалить в фоновом режиме. Это исключает ситуацию, когда Nginx записывает в каталог, который одновременно удаляется.
mv /var/cache/nginx /var/cache/nginx_old
mkdir /var/cache/nginx
chown nginx:nginx /var/cache/nginx
kill -USR2 $(cat /var/run/nginx.pid)Сигнал USR2 заставляет Nginx переоткрыть файловые дескрипторы, после чего он начинает использовать новый каталог. Старый каталог можно удалить позже, когда сервер не под нагрузкой:
rm -rf /var/cache/nginx_oldЭтот метод работает для proxy_cache. Для open_file_cache очистка не требуется: записи автоматически выбывают по истечении inactive или при изменении файла, если valid истёк.
Для точечной инвалидации отдельных записей используйте модуль ngx_cache_purge или коммерческий Nginx Plus. В открытой версии Nginx можно реализовать условный байпас через proxy_cache_bypass с секретным заголовком:
location /images/ {
proxy_cache my_cache;
proxy_cache_bypass $http_x_purge_token;
proxy_no_cache $http_x_purge_token;
}Запрос с заголовком X-Purge-Token: secret обойдёт кэш и обновит запись. Это простой способ инвалидации без сторонних модулей.
Интеграция с CDN: эффективное хранение и доставка медиа
Nginx в роли origin-сервера для CDN должен отдавать корректные заголовки кэширования, чтобы CDN эффективно сохранял статику и не запрашивал origin при каждом обращении. Ошибки в заголовках приводят к избыточному трафику между CDN и origin, что увеличивает задержки и расходы.
Настройка заголовков кэширования для CDN
Для статических файлов с версионированием в имени (style.v123.css, image_abc123.jpg) задавайте длительный срок жизни и immutable:
location ~* \.(jpg|jpeg|png|gif|ico|webp|avif|css|js|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}Разбор значений Cache-Control:
- public - ответ может кэшироваться любыми промежуточными узлами, включая CDN.
- immutable - клиент не должен отправлять условные запросы с If-Modified-Since в течение срока жизни. Это снижает количество запросов к CDN и origin.
- no-cache - ответ можно кэшировать, но перед использованием необходимо проверить актуальность на origin.
- private - ответ предназначен только для конкретного пользователя и не должен кэшироваться CDN.
Для изображений, которые могут меняться без изменения имени файла, используйте более короткий срок и разрешите revalidation:
location ~* \.(jpg|jpeg|png|webp)$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}Очистка кэша на CDN
После очистки локального кэша Nginx необходимо очистить кэш на CDN. Иначе пользователи продолжат получать устаревшие данные с edge-серверов. Большинство CDN предоставляют API для purge:
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
--data '{"files":["https://example.com/images/product.jpg"]}'Для массовой очистки используйте purge по префиксу или тегам. Настройте тегирование кэша на origin, чтобы очищать группы файлов одним вызовом:
add_header Cache-Tag "images,product-123";Затем на CDN выполните purge по тегу product-123 - все связанные изображения будут инвалидированы.
Прогрев кэша после очистки: как избежать пиковых нагрузок
После очистки кэша первые запросы пользователей идут к бэкенду и создают пиковую нагрузку. Прогрев кэша - это предварительный запрос популярных URL до того, как их запросят реальные пользователи. Так бэкенд заполняет кэш постепенно, без лавины.
Скрипт для прогрева кэша
Список популярных URL можно получить из access.log:
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -1000 | awk '{print $2}' > /tmp/popular_urls.txtСкрипт прогрева на bash с ограничением скорости:
#!/bin/bash
BASE_URL="http://localhost"
RATE_LIMIT="50" # запросов в секунду
while IFS= read -r url; do
curl -s -o /dev/null "${BASE_URL}${url}"
sleep $(echo "scale=3; 1/${RATE_LIMIT}" | bc)
done < /tmp/popular_urls.txtЗапускайте скрипт после очистки кэша, когда сервер не под максимальной нагрузкой. Для больших списков URL используйте параллельные запросы с ограничением через xargs:
cat /tmp/popular_urls.txt | xargs -P 10 -I {} curl -s -o /dev/null "http://localhost{}"Параллельность 10 при 50 RPS создаёт умеренную нагрузку на бэкенд, достаточную для заполнения кэша без риска перегрузки.
Использование proxy_cache_lock для предотвращения stampede
Cache stampede - ситуация, когда множество одновременных запросов к одному ресурсу, отсутствующему в кэше, порождают множество параллельных запросов к бэкенду. Директива proxy_cache_lock решает эту проблему:
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_lock_age 10s;При включённом lock только один запрос проходит к бэкенду, остальные ждут до lock_timeout. Если за это время кэш не заполнен, ожидающие запросы получают ответ от бэкенда напрямую. Это снижает пиковую нагрузку при холодном кэше в несколько раз.
Типичные ошибки при управлении кэшем и как их избежать
Большинство проблем с кэшем Nginx возникает из-за неправильных параметров или отсутствия мониторинга. Разберём четыре типичные ошибки.
Ошибка 1: слишком большой max в open_file_cache без учёта памяти. При max=100000 и длительном inactive кэш метаданных может занять сотни мегабайт. На сервере с 1 ГБ RAM это приведёт к своппингу и деградации. Решение: рассчитывайте память как max * 200 байт и не превышайте 10–20% доступной RAM.
Ошибка 2: отсутствие stale-кэша при очистке. Если proxy_cache_use_stale не настроен, любая ошибка бэкенда после очистки кэша приводит к ошибкам 502/504 для пользователей. Решение: всегда настраивайте stale для error, timeout, updating и кодов 500–504.
Ошибка 3: очистка кэша без учёта CDN. Локальный кэш очищен, но CDN продолжает отдавать устаревшие данные с edge-серверов. Пользователи видят старый контент, и вы получаете ложное впечатление, что очистка не сработала. Решение: автоматизируйте purge на CDN в том же скрипте, что очищает локальный кэш.
Ошибка 4: игнорирование proxy_cache_lock. Без lock при холодном кэше и высоком трафике бэкенд получает десятки параллельных запросов на один ресурс. Это может привести к перегрузке и каскадному отказу. Решение: включите proxy_cache_lock с разумным timeout.
Мониторинг эффективности кэша
Без мониторинга вы не узнаете, работает ли кэш. Включите stub_status для базовой статистики:
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}Для детального мониторинга используйте Prometheus exporter. Ключевые метрики:
- hit ratio - отношение попаданий в кэш к общему числу запросов. Для статики должно быть выше 90%. Если ниже - проверяйте ключи кэша и сроки жизни.
- количество запросов к бэкенду - резкий рост после очистки указывает на недостаточный прогрев.
- использование памяти - рост без стабилизации сигнализирует об утечке в open_file_cache.
Настройте алерты на падение hit ratio ниже 70% и на рост ошибок 502/504. Это позволит заметить проблему до того, как она повлияет на пользователей.
Заключение: чек-лист безопасного управления кэшем
Безопасное управление кэшем Nginx для статики и изображений сводится к пяти шагам:
- Настройте open_file_cache с параметрами, рассчитанными под вашу память и количество файлов. Начните с max=5000, inactive=30s, valid=60s и корректируйте по мониторингу.
- Включите proxy_cache_use_stale для error, timeout, updating и кодов 500–504. Это защитит пользователей от ошибок при очистке кэша.
- Используйте атомарное переименование каталога для массовой очистки и proxy_cache_bypass для точечной инвалидации.
- Настройте заголовки Cache-Control для CDN и автоматизируйте purge на CDN вместе с локальной очисткой.
- Прогревайте кэш после очистки скриптом на основе access.log и включите proxy_cache_lock для защиты от stampede.
Тестируйте все изменения на staging-окружении перед применением в production. Начните с малого: настройте open_file_cache, затем добавьте stale-кэш, и только после этого автоматизируйте очистку и прогрев. Мониторинг hit ratio и ошибок бэкенда покажет, насколько эффективно работает ваша конфигурация.