Кэш перед приложением — самый дешёвый способ ускорить сайт: PHP и база данных перестают выполняться на каждый запрос, а готовый HTML отдаётся за миллисекунды. Связка Nginx и Varnish закрывает две разные задачи: Varnish хранит ответы приложения и умеет точечно их сбрасывать, а Nginx обслуживает статику, завершает TLS и страхует динамику микрокэшем. Менять код приложения не нужно — достаточно расставить заголовки и правила обхода кэша.

Схема и роли компонентов

Varnish не умеет работать с TLS, поэтому на входе ставим Nginx с сертификатом, а Varnish слушает внутренний порт. Типовая цепочка выглядит так: клиент → Nginx :443 → Varnish :6081 → Nginx :8080 → PHP-FPM. Статику первый Nginx может отдавать сам, не пропуская запрос через кэш. Если сайт работает только по HTTP внутри доверенной сети, Varnish можно поставить и на восьмидесятый порт.

  • Varnish отвечает за полные HTML-страницы и инвалидацию по URL.
  • Nginx завершает TLS, отдаёт файлы и ограничивает методы.
  • PHP-FPM и база данных получают только промахи кэша.
  • Заголовки Cache-Control и Set-Cookie становятся частью контракта между приложением и кэшем.
sudo apt install varnish
sudo systemctl status varnish
varnishd -V
ss -ltnp | grep -E ':(80|443|6081|8080)'

Базовая конфигурация Varnish

Файл /etc/varnish/default.vcl описывает бэкенд, проверку доступности и правила кэширования. Проверка probe важна: если приложение перезапускается, Varnish временно исключит узел из ротации, а не будет отдавать пятисотые из кэша.

vcl 4.1;

acl purge {
    "localhost";
    "10.0.0.0"/8;
}

backend default {
    .host = "127.0.0.1";
    .port = "8080";
    .probe = {
        .url = "/healthz";
        .timeout = 2s;
        .interval = 5s;
        .window = 5;
        .threshold = 3;
    }
}

В vcl_recv мы запрещаем кэшировать личные разделы, запросы с авторизацией и сессионными cookie, а также разрешаем сброс только из доверенных сетей. В vcl_backend_response задаём время жизни: статике — сутки, обычным страницам — несколько минут, ответам с Set-Cookie — ноль.

sub vcl_recv {
    if (req.method == "PURGE") {
        if (!client.ip ~ purge) {
            return (synth(405, "Not allowed"));
        }
        return (purge);
    }
    if (req.url ~ "^/(admin|cart|checkout|account)") {
        return (pass);
    }
    if (req.http.Authorization) {
        return (pass);
    }
    if (req.http.Cookie ~ "sessionid|csrftoken") {
        return (pass);
    }
    unset req.http.Cookie;
}

sub vcl_backend_response {
    if (bereq.url ~ "^/static/") {
        set beresp.ttl = 1d;
        set beresp.grace = 1h;
        unset beresp.http.Set-Cookie;
    } else if (beresp.status == 200 && !beresp.http.Set-Cookie) {
        set beresp.ttl = 5m;
        set beresp.grace = 30m;
    }
    if (beresp.http.Cache-Control ~ "no-store|private") {
        set beresp.uncacheable = true;
        set beresp.ttl = 0s;
    }
}

sub vcl_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
}

После правки проверяем синтаксис и перезапускаем службу. Ошибка в VCL не роняет работающий процесс до перезапуска, поэтому старую конфигурацию можно спокойно откатить.

sudo varnishd -C -f /etc/varnish/default.vcl > /dev/null
sudo systemctl reload varnish
sudo systemctl status varnish --no-pager

Микрокэш в Nginx на источнике

Второй уровень нужен не для дублирования, а для защиты от всплесков: когда Varnish пропускает запрос из-за cookie или кэш только что очищен, Nginx может отдать копию возрастом в несколько секунд. Для анонимных страниц это снижает нагрузку на PHP в десятки раз. Включаем отдельную зону и запрещаем кэшировать ответы для авторизованных пользователей. Зону и map объявляем в контексте http, а location — внутри server.

fastcgi_cache_path /var/cache/nginx/fcgi levels=1:2 keys_zone=fcgi:32m inactive=15m max_size=4g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_lock on;
fastcgi_cache_valid 200 10s;
add_header X-FastCGI-Cache $upstream_cache_status;

map $http_cookie $skip_cache {
    default 0;
    "~*sessionid|csrftoken|wordpress_logged_in" 1;
}

location ~ \.php$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_cache fcgi;
    fastcgi_cache_bypass $skip_cache;
    fastcgi_no_cache $skip_cache;
    include fastcgi_params;
}

Заголовок X-FastCGI-Cache показывает HIT, MISS или BYPASS. Если на странице появляется имя пользователя, значит, правило обхода не поймало cookie и кэш отдаёт чужие данные — такую страницу нужно срочно исключить.

Инвалидация и диагностика

Сброс по URL через ban асинхронен и не требует перезапуска. Команда purge удаляет конкретный объект, ban — все совпадения по регулярному выражению. После публикации новой статьи или изменения цены достаточно отправить ban на раздел.

varnishadm "ban req.url ~ ^/catalog/"
curl -sI https://example.com/catalog/ | grep -iE 'age|x-cache|via'
varnishstat -1 -f MAIN.cache_hit -f MAIN.cache_miss
varnishlog -g request -q 'ReqURL ~ "^/catalog/"'

Заголовок Age больше нуля означает попадание в кэш. varnishstat показывает счётчики хитов и промахов, по ним считают hit rate: для контентного сайта нормально 80–95 %, для личного кабинета кэш почти не работает. В varnishlog ищем причины обхода: VCL_call PASS, Fetch, Hit.

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

  • Кэширование ответа с Set-Cookie — пользователи получают чужие сессии.
  • Забытый Vary: Cookie или, наоборот, лишний Vary: User-Agent, который дробит кэш.
  • Сохранение ответов 404 и 500 на часы — после исправления ошибка живёт в кэше.
  • Отсутствие обхода для Authorization и служебных путей вроде /healthz.
  • Слишком маленькое хранилище: по умолчанию Varnish может использовать немного памяти, размер задаётся в /etc/systemd/system/varnish.service.d/override.conf.

Чек-лист перед включением

  • Проверить, что все динамические страницы отдают корректный Cache-Control.
  • Убедиться, что форма входа и корзина исключены из кэша.
  • Сравнить HTML из кэша и с источника: возраст, cookie, CSRF-токены.
  • Настроить мониторинг hit rate и алерт на падение ниже порога.
  • Описать процедуру сброса кэша при деплое.

Итог

Nginx и Varnish не конкурируют, а дополняют друг друга: первый отвечает за TLS, статику и защиту источника, второй — за агрессивное кэширование HTML с управляемой инвалидацией. Начните с малого TTL и узкого списка исключений, измерьте hit rate, и только потом увеличивайте время жизни. Главное правило — кэшировать можно только то, что не зависит от конкретного пользователя, а всё остальное должно уходить в pass.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте