Встроенный HTTP cache в HAProxy — лёгкий способ ускорить ответы и разгрузить origin без отдельного слоя вроде Varnish. Он особенно полезен, когда уже используете HAProxy как reverse proxy и балансировщик, а кэшировать нужно небольшие и часто запрашиваемые объекты: статические JSON/конфиги, небольшие изображения и иконки, ответы health/status, метаданные. Если вы планируете держать HAProxy на выделенном сервере, удобно развернуть его на облачном VDS — так проще масштабировать и контролировать ресурсы.
Как работает HAProxy HTTP cache: краткий обзор
Кэш HAProxy хранит объекты в оперативной памяти, использует политику вытеснения LRU и обслуживает запросы напрямую из процесса прокси. Это даёт очень низкую задержку ответа и минимальную нагрузку на origin, но накладывает ограничения:
- Память: общий объём кэша ограничивается конфигурацией и физической RAM; кэш «дорогой», большие файлы хранить невыгодно.
- Ключ: базовый ключ — URL запроса (путь и query). Это хорошо для версионируемых URL и статических путей, но требует нормализации запросов (срезать «мусорные» параметры, фиксировать порядок параметров, если нужно).
- TTL: срок жизни берётся из ответа origin (директивы
Cache-ControlиExpires) или дефолтногоmax-ageв секцииcache, если заголовков нет. Фоновой ре-валидации нет. - Функциональность: это не полноценный кэш с глубоким учётом
Varyи условной валидацией; ориентируйтесь на «тёплые» небольшие ответы и простые правила.
Именно из-за этих ограничений HAProxy-кэш отлично выступает как edge-оптимизация, но не претендует на роль CDN или сложного фрагментного кэширования.
Когда включать HAProxy HTTP cache
Практические кейсы, где кэш в HAProxy окупается максимально быстро:
- Короткие JSON/JS/конфигурационные эндпойнты (метаданные, роутинг, настройки), запрашиваемые тысячами клиентов.
- Микросервисы со «соглашённой» консистентностью: допустимо обновление раз в 30–300 секунд.
- Статика повышенного спроса, но небольшого объёма: иконки, SVG, фавиконки, маленькие PNG, открытые шрифты.
- API с версионируемыми URL (cache-busting через версии в пути/параметрах) и одинаковыми ответами для многих клиентов.
Избегайте кэшировать большие файлы, ответы с персональными данными или зависящие от авторизации/куков, а также всё, что отдаётся с учётом устройства/языка без нормализации заголовков.

Базовая схема конфигурации
Минимально рабочая конфигурация кэша в HAProxy: объявляем секцию cache с лимитами, включаем cache-use на запросах и отмечаем ответы для сохранения через cache-store при нужных условиях.
# Раздел cache: лимиты и дефолтный TTL
cache mycache
total-max-size 256
max-object-size 1m
max-age 300
# Фронтенд (пример)
frontend fe_http
bind :80
mode http
option httplog
# Обслуживать из кэша, если объект найден
http-request cache-use mycache
default_backend be_app
# Бэкенд к приложению
backend be_app
mode http
# Кэшируем только успешные GET-ответы без приватных директив
acl is_get method GET
acl ok_status status 200
acl no_private res.hdr(Cache-Control) -m sub private
acl no_nostore res.hdr(Cache-Control) -m sub no-store
http-response cache-store mycache if is_get ok_status no_private no_nostore
server app1 10.0.0.10:8080 check
server app2 10.0.0.11:8080 check
Здесь мы сознательно не кэшируем ответы, если сервер помечает их Cache-Control: private или no-store. За основу TTL берётся Cache-Control: max-age или Expires (если сервер их вернул), иначе — дефолт max-age из секции cache.
Дизайн ключа кэша и нормализация запросов
Поскольку ключ кэша определяется URL, важно сделать ответы детерминированными для одинаковых запросов. Несколько практических шагов:
- Нормализуйте бессмысленные параметры: если есть трекинг-параметры (например,
utm_*) и они не влияют на ответ, срезайте их на уровне приложения или маршрутизации. - Единый формат: порядок параметров, регистр в пути и т. п. Лучше обеспечивать на уровне приложения, часть можно выровнять на входе.
- Выносите вариативность в версию ресурса:
/api/v2/config.jsonили/config.json?v=2025-02-01. Замена версии — «инвалидирование» кэша без purge.
Если у вас есть варианты ответа по языку/региону/платформе, избегайте зависимостей от непредсказуемых headers. Лучше явно отражайте их в URL (например, /v2/ru/config.json), чтобы ключ кэша был однозначным.
Учёт заголовков: что пропускать и что нормализовать
Правила простые, но критичные для безопасности и корректности:
- Не кэшируйте, если присутствует
Authorizationили сессионныеCookie. Проще всего запретитьcache-storeпри таких заголовках и/или разделить локации на публичные и приватные. - Избегайте зависимостей от
Accept-Encoding, если не готовы к аккуратной нормализации. В идеале отдавайте на origin либо без сжатия, либо стабильно сжатую версию. Смешение сжатого и несжатого безVaryприведёт к неверной раздаче. - Сохраняйте
ETagиLast-Modified— клиенты смогут валидировать объекты напрямую с origin после истечения TTL. Сам HAProxy не делает условную валидацию и просто перестаёт использовать истёкший объект.
# Запрет кэширования при наличии авторизации или кук
backend be_app
acl is_get method GET
acl ok_status status 200
acl no_private res.hdr(Cache-Control) -m sub private
acl no_nostore res.hdr(Cache-Control) -m sub no-store
acl has_auth req.hdr(Authorization) -m found
acl has_cookie req.hdr(Cookie) -m found
http-response cache-store mycache if is_get ok_status no_private no_nostore !has_auth !has_cookie
Выбор TTL и стратегии обновления
Выбор TTL — баланс между «свежестью» и разгрузкой origin. Несколько практик:
- Для простых статических JSON/JS: 60–300 секунд. Клиенты редко страдают от минутной задержки обновления.
- Для иконок/шрифтов: 1 час и больше при версионировании в URL. Тогда «вечный» кэш допустим.
- Для чувствительных метаданных: 10–30 секунд, и обязательно предусмотреть быструю смену версии URL для «тёплой» прокатки.
Если origin рассылает корректные Cache-Control, используйте их как источник истины. Если нет — задавайте дефолты в секции cache и постепенно переводите управление TTL на сторону приложения.
Что не стоит кэшировать
Лучше исключить из HAProxy HTTP cache следующее:
- Крупные медиа-файлы и архивы: RAM-дорогие, часто с
Range-запросами, ограниченная выгода (см. разбор нюансов в статье про Range и кэш). - Персонализированный контент и любые ответы, зависящие от сессии, куков или авторизации.
- Ответы с высокими требованиями к консистентности (платёжные статусы, корзины) — здесь лучше кэш приложения с точной инвалидацией.
Наблюдаемость: как понять, что кэш работает
В полевых условиях удобно проверять кэш через повторные запросы и заголовки. Появление Age на повторном запросе — хороший признак кэш‑хита на стороне прокси.
# Простой ручной тест
curl -I http://your-proxy.example/config.json
sleep 1
curl -I http://your-proxy.example/config.json
Для постоянной наблюдаемости используйте:
- Логи HAProxy с метриками времени ответа: падение латентности и количества обращений к backend — основной индикатор.
- Счётчики backend (из stats) — уменьшение RPS на приложении при стабильном клиентском трафике означает, что кэш снимает нагрузку.

Производительность и лимиты памяти
Основные «ручки» для планирования объёма кэша:
total-max-size— общий лимит RAM под кэш. Начинайте скромно (128–512 МБ) и расширяйте по факту.max-object-size— максимальный размер объекта. Ставьте потолок, чтобы случайные крупные файлы не «выели» кэш.- Доля «горячих» объектов: чем более Zipf-подобный запросный поток, тем выше отдача даже при небольшом объёме.
Помните, что кэш живёт в процессе HAProxy. Планируйте запас под буферы, TLS и сжатие (если включено).
Безопасность и гигиена заголовков
Кэш — это не только про скорость, но и про безопасность. Несколько правил гигиены:
- Не кэшируйте ответы с
Set-Cookie, если они могут течь между пользователями. Общее правило — любые ответы сSet-Cookieисключать. - Уважайте директивы
Cache-Controlorigin:private,no-store,no-cache— запрет наcache-store. При необходимости делайте адресные исключения. - Не смешивайте gzip/identity без нормализации. Если нужно, приводите
Accept-Encodingк единому значению или разносите варианты по URL.
Если вы терминируете TLS на фронте, заранее позаботьтесь о сертификатах. Для продакшена подойдут проверенные SSL-сертификаты.
Инвалидация: как жить без purge
Встроенный кэш HAProxy не предоставляет точечного purge по ключу. На практике помогает версия в URL: меняете версию — получаете новое пространство ключей и быстрый rollout. Для оперативных ситуаций держите возможность коротко урезать TTL (через конфиг приложения или заголовки) на время, пока изменения раскатятся.
Полный сброс кэша как крайняя мера — через перезапуск/перечтение конфигурации; это затрагивает соединения и не годится как регулярный инструмент. Когда нужна строгая инвалидация и сложные зависимости, используйте кэш приложения — см. кэширование на стороне приложения (Memcached/Redis).
Типовые рецепты правил
Кэшировать только «белый список» путей
acl cache_path path_beg /static/ /assets/ /public-api/
acl is_get method GET
acl ok_status status 200 301 404
acl has_auth req.hdr(Authorization) -m found
acl has_cookie req.hdr(Cookie) -m found
acl no_private res.hdr(Cache-Control) -m sub private
acl no_nostore res.hdr(Cache-Control) -m sub no-store
http-response cache-store mycache if is_get cache_path ok_status !has_auth !has_cookie no_private no_nostore
Уважать TTL origin, но иметь дефолт
Если приложение не расставляет Cache-Control, используем дефолт из секции cache. Когда заголовки появятся, HAProxy применит их автоматически — удобный переходный период.
Отдельный кэш для «живых» API и для статики
Если хотите по-разному ограничивать размер объектов и TTL, заведите две секции cache и используйте соответствующие правила cache-store/cache-use на нужных локациях. Например, для API — max-object-size 64k, для статики — 512k и больше.
Тестирование и контроль качества
План тестов перед включением кэша в прод:
- Соберите список эндпойнтов-кандидатов: только GET, публичные, без персонализации.
- Проверьте корректность заголовков origin: отсутствие
Set-Cookie, ожидаемыеCache-Control, отсутствие приватных директив. - Включите кэш в стейджинге, используйте повторные запросы и сравнивайте тайминги. Убедитесь, что логика приложения не нарушилась.
- Проверьте обновление контента: смените версию URL, убедитесь, что новые клиенты получают новую версию, а старая живёт до истечения TTL.
- Зафиксируйте алерты: рост 5xx на backend, неожиданные коды 304/412 у клиентов, всплески латентности — поводы пересмотреть правила.
Частые ошибки и как их избежать
- Кэширование ответов с куками. Лечится жёстким запретом
cache-store, если естьCookieилиSet-Cookie. - Смешение gzip/identity. Либо нормализуйте
Accept-Encoding, либо выключите сжатие на origin для кэшируемых локаций, либо разнесите по URL. - Кэширование слишком крупных объектов. Подрежьте
max-object-size, ведите «белые списки» путей. - Отсутствие версии в URL. Любые «вечные» TTL безопасны только при версионировании имён файлов/путей.
- Попытка использовать кэш как механизм строгой консистентности. Для этого нужен application-level cache с управляемой инвалидацией.
Пошаговая стратегия внедрения
Рекомендую идти от простого к сложному:
- Шаг 1. Включите кэш на узком наборе статических локаций. Отследите эффект на RPS и latency backend.
- Шаг 2. Добавьте «лёгкие» API (конфиги, справочники) с TTL 60–120 секунд. Убедитесь, что для них нет зависимости от
Cookie/Authorization. - Шаг 3. Отрефакторьте заголовки origin: выставляйте корректные
Cache-Control, чтобы управлять TTL централизованно. - Шаг 4. Разделите кэш-пулы при необходимости, чтобы задать разные лимиты размера объектов и сроков хранения.
- Шаг 5. Введите регламент: как «прокатывать» изменения через версию URL и как быстро сворачивать TTL в инцидентах.
Итог
HAProxy HTTP cache — прагматичный инструмент для ускорения публичных GET-ответов и снижения нагрузки на origin, когда вам уже нужен reverse proxy и нет желания поднимать отдельный кэширующий слой. Он прост, предсказуем, хорошо вписывается в инсталляции и дисциплинирует подход к headers, URL-версии и TTL. Ключ к успеху — держать область применения разумной: только публичный контент, маленькие объекты, чёткая нормализация запросов и уважение к Cache-Control. Тогда кэш будет работать быстро и безопасно, без неприятных сюрпризов.


