OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

Rate limit API в HAProxy: практическое использование stick-tables против DDoS и брутфорса

Разбираем, как на практике настроить rate limit для HTTP API в HAProxy с использованием stick-tables. Поговорим о простых лимитах по IP и более сложных схемах с учётом пути, ключа API и статуса ответа. В статье приведены примеры конфигураций, подходы к выбору порогов, защите от DDoS и брутфорса, а также приёмы отладки.
Rate limit API в HAProxy: практическое использование stick-tables против DDoS и брутфорса

Когда API живёт за HAProxy, один из первых вопросов — как ограничить шумный трафик и не положить бэкенды. Особенно когда появляются агрессивные боты, кривые интеграции, а иногда и DDoS-подобные атаки. Встроенные stick-tables в HAProxy дают очень мощный и при этом лёгкий способ реализовать гибкий rate limit без внешних сервисов.

В этом разборе посмотрим, как строить rate limit для HTTP API на базе stick-tables: от простых лимитов по IP до более сложных сценариев — с учётом пути, ключа API, юзер-агента и статуса ответа. Параллельно обратим внимание на типичные грабли и отладку.

Зачем вообще делать rate limit на уровне HAProxy

Ограничивать запросы можно на уровне приложения, WAF, API-gateway, но HAProxy остаётся очень выгодной точкой для rate limit:

  • Ранний отсев мусора — плохие запросы режутся до попадания в приложение.
  • Глобальное хранилищеstick-tables видят весь трафик через инстанс, а не только отдельный под или воркер.
  • Простая логика — декларативные ACL без Lua и внешних сервисов.
  • Минимальный оверхед — счётчики в памяти без диска и сетевых запросов.

Типичные задачи, которые удобно решать rate limit на HAProxy:

  • Защита API от DDoS-подобной нагрузки по IP или подсетям.
  • Ограничение брутфорса логина по IP или учётной записи.
  • Срезание «шумных» клиентов (битые интеграции, баги в клиентах).
  • Разные лимиты для публичного и приватного API.

Если у вас API фронтируется через балансировщик на базе VDS с HAProxy, вынесенный rate limit на уровень прокси позволяет защитить сразу несколько приложений без доработок кода.

Базовая теория stick-tables в HAProxy

Stick-tables — это кэши в памяти, которые хранят ключи (например, IP клиента) и набор полей store: счётчики, скользящие окна, метки времени и т. д.

Минимальная структура может выглядеть так:

backend api_ratelimit
  stick-table type ip size 1m expire 10m store http_req_rate(10s)

Расшифровка параметров:

  • type ip — ключом является IP клиента.
  • size 1m — максимум 1 миллион записей (подбирается по нагрузке и памяти).
  • expire 10m — запись живёт 10 минут после последнего доступа.
  • store http_req_rate(10s) — храним скорость запросов за последние 10 секунд.

Дальше в фронтенде или бэкенде мы «приклеиваем» трафик к таблице с помощью stick on или track-sc и читаем значения в ACL.

Схема обработки запросов к API в HAProxy с rate limit по IP через stick-tables

Простой rate limit API по IP

Начнём с самого частого кейса: ограничить количество HTTP-запросов к API с одного IP-адреса, допустим, до 20 RPS.

Минимальный пример конфигурации

frontend api_frontend
  bind :80

  acl is_api path_beg /api/
  use_backend api_backend if is_api

backend api_backend
  stick-table type ip size 100k expire 10m store http_req_rate(10s)

  # Учитываем каждый запрос по IP-адресу клиента
  http-request track-sc0 src

  # Порог: больше 200 запросов за 10 секунд (20 RPS)
  acl too_many_requests sc0_http_req_rate gt 200

  # Отбиваем лишние запросы до обращения к приложению
  http-request deny status 429 content-type application/json if too_many_requests

  default-server init-addr last,libc
  server api1 10.0.0.10:8080 check
  server api2 10.0.0.11:8080 check

Ключевые моменты:

  • stick-table описана в бэкенде, где мы считаем запросы.
  • http-request track-sc0 src — клиентский IP попадает в stick-table (счётчик обновляется на каждый запрос).
  • sc0_http_req_rate — обращение к полю http_req_rate(10s) в нулевом stick-counter.
  • gt 200 — лимит: больше 200 запросов за 10 секунд (20 RPS).
  • http-request deny с status 429 — сразу возвращаем ошибку клиенту.

Важно: порядок правил

Порядок директив http-request имеет значение. Сначала должны идти track-sc, потом ACL, потом deny, set-header и прочее. Иначе вы будете смотреть в неинициализированный счётчик и получать нулевые значения.

Скользящее окно и выбор размера

Параметр http_req_rate(10s) задаёт длину окна: 10 секунд. Если вы ждёте трафик «рывками», обычно лучше брать чуть большее окно (например 30–60 секунд), чтобы не резать клиентов за короткий всплеск.

Общая идея: чем больше окно, тем мягче реакция на кратковременные пики, но тем дольше злой клиент остаётся «наказанным».

Типичные варианты для API:

  • Публичный REST API: http_req_rate(10s) с лимитом 20–50 RPS.
  • Внутренний API: 100–300 RPS, иногда без строгих лимитов (только защита от аномалий).
  • Эндпоинт логина: http_req_rate(60s) с небольшим порогом (5–10 запросов в минуту).

Практический приём: сначала соберите метрики по текущему трафику (по логам или мониторингу), а уже потом подбирайте окна и пороги. Слепой выбор значений почти всегда приводит к ложным срабатываниям.

Разные лимиты для разных путей API

Обычно у API нет одного глобального лимита. Примеры:

  • /api/auth/login — строгий лимит для защиты от брутфорса.
  • /api/public/ — мягкий лимит, много анонимных клиентов.
  • /api/admin/ — небольшой трафик, можно ставить жёсткий лимит и дополнительные проверки.

Настроим разные пороги в одном бэкенде:

backend api_backend
  stick-table type ip size 200k expire 15m store http_req_rate(10s),http_req_rate(60s)

  http-request track-sc0 src

  acl path_login  path_beg /api/auth/login
  acl path_public path_beg /api/public/

  acl too_many_login  path_login  sc0_http_req_rate(60s) gt 20
  acl too_many_public path_public sc0_http_req_rate(10s) gt 300

  http-request deny status 429 if too_many_login
  http-request deny status 429 if too_many_public

  server api1 10.0.0.10:8080 check

Здесь:

  • В таблице мы храним два счётчика: для 10 и 60 секунд.
  • ACL совмещают путь и IP: логин режем по медленному окну, публичный API — по быстрому.

Если вы уже используете HAProxy для балансировки HTTP/2 или gRPC, полезно посмотреть общий обзор работы stick-tables и примеров ACL в статье о настройке HAProxy для HTTP/2 и gRPC.

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

Лимит не по IP, а по ключу API или токену

Лимит по IP — грубый инструмент. Один IP может принадлежать большому количеству пользователей (NAT, корпоративная сеть). Чаще нужно лимитировать по:

  • ключу API (например, в X-API-Key);
  • JWT-токену (часто в Authorization: Bearer ...);
  • ID клиента в query-параметрах.

Ключевая идея: в stick-table можно хранить не только IP, но и произвольные строки (type string), главное — контролировать длину.

Rate limit по заголовку X-API-Key

backend api_ratelimit_key
  stick-table type string len 64 size 500k expire 10m store http_req_rate(30s)

  # Забираем ключ из заголовка
  http-request set-var(txn.apikey) req.hdr(X-API-Key)

  # Не трекаем пустые ключи
  acl missing_key var(txn.apikey) -m len 0
  http-request deny status 401 if missing_key

  # Для валидных ключей считаем rate
  http-request track-sc0 var(txn.apikey)
  acl too_many_by_key sc0_http_req_rate gt 300

  http-request deny status 429 if too_many_by_key

  server api1 10.0.0.20:8080 check

Нюансы:

  • type string len 64 — ограничиваем длину ключа, чтобы кто-то не принёс килобайтный мусор и не раздувал память.
  • Если ключ отсутствует, сразу возвращаем 401, не трогая таблицу.
  • Лимит завязан именно на ключ, а не на IP клиента.

Комбинированный ключ: IP + User-Agent или IP + путь

Иногда IP сам по себе — слишком грубый идентификатор, а отдельная строка — наоборот слишком «мелкая». Можно собирать составной ключ из нескольких частей.

Пример: хотим ограничить RPS по тройке «IP + путь + метод» для более тонкого контроля.

backend api_ratelimit_combo
  stick-table type string len 128 size 500k expire 10m store http_req_rate(10s)

  # Собираем составной ключ
  http-request set-var(txn.rate_key) src,,:,method,,:,path

  http-request track-sc0 var(txn.rate_key)

  acl too_fast sc0_http_req_rate gt 100
  http-request deny status 429 if too_fast

  server api1 10.0.0.30:8080 check

В реальности лучше опираться на src, если HAProxy — граничный прокси. Если перед ним есть ещё уровень балансировки, сначала стоит аккуратно валидировать доверенные прокси и заголовки типа X-Forwarded-For.

Защита от брутфорса логина

Классический сценарий: атакующий перебирает пароли на /api/auth/login. Нужно ограничить количество неуспешных попыток по IP, но не наказывать нормального пользователя за пару неверных вводов.

Лимит по IP + статусу ответа

Тонкость: считать надо не все запросы, а только неуспешные логины (например, 401/403). Но статус ответа мы узнаём только после обращения к бэкенду. Для этого есть http-response track-sc.

backend api_auth
  stick-table type ip size 100k expire 30m store http_err_rate(60s)

  # Привязываем IP к таблице
  http-request track-sc0 src

  acl is_login path_beg /api/auth/login

  # На бэкенд пускаем всегда, но следим за ответом
  http-response track-sc0 src if is_login status 401 403

  acl too_many_failed sc0_http_err_rate gt 20

  # Если накопилось много ошибок - режем запросы на входе
  http-request deny status 429 if is_login too_many_failed

  server auth1 10.0.0.40:8080 check

Здесь важно:

  • Используем http_err_rate(60s) — считаем только ошибки (401/403), а не весь трафик.
  • Блокируем запросы уже на стадии http-request, если по IP накопилось слишком много фейлов.
Виртуальный хостинг FastFox
Виртуальный хостинг для сайтов
Универсальное решение для создания и размещения сайтов любой сложности в Интернете от 95₽ / мес

Базовая защита от DDoS API с помощью stick-tables

Серьёзные DDoS-атаки лучше отбивать на сетевом уровне (файрволы, анти-DDoS у провайдера), но stick-tables помогают в случаях, когда атака идёт по HTTP-уровню, с относительно небольшим количеством IP и без чудовищного PPS.

Что можно сделать без тяжёлой артиллерии:

  • Ограничить RPS по IP (как в базовом примере).
  • Отсекать клиентов, которые постоянно получают 4xx/5xx (похожие на сканеры и ботов).
  • Временно «замораживать» особо шумных клиентов (soft ban).

Soft ban с помощью stick-tables

Отдельная таблица может хранить «бан-лист» IP-адресов с таймаутом.

backend api_banlist
  stick-table type ip size 100k expire 10m store gpc0

backend api_main
  stick-table type ip size 500k expire 10m store http_req_rate(10s),http_err_rate(60s)

  # Основной учёт по IP
  http-request track-sc0 src

  # Вторая таблица - банлист
  http-request track-sc1 src table api_banlist

  # Если IP уже в банлисте - сразу 429
  acl banned sc1_get_gpc0 gt 0
  http-request deny status 429 if banned

  # Слишком много ошибок - записываем в банлист
  acl too_many_errors sc0_http_err_rate gt 50
  http-request sc-inc-gpc0(0) src table api_banlist if too_many_errors

  server api1 10.0.0.10:8080 check

Здесь используется:

  • gpc0 — простое целочисленное поле (general purpose counter) в таблице банлиста для пометки IP.
  • Отдельный stick-counter sc1, указывающий на таблицу api_banlist.

Схема упрощена, но даёт идею: при превышении порога ошибок IP попадает в бан-таблицу на 10 минут (по expire), и дальше все запросы от него режутся сразу.

Схема soft ban в HAProxy с использованием stick-tables и временного бан-листа IP

Нюансы производительности и памяти

Stick-tables хранятся в оперативной памяти процесса HAProxy. Их легко перегрузить, если бездумно ставить огромные size, длинные строки и десяток полей store.

На что обратить внимание:

  • size — максимальное количество ключей. Каждому ключу соответствует объект в памяти (десятки байт плюс длина ключа для строк).
  • expire — чем он больше, тем дольше «висят» мёртвые ключи. Для активного публичного API 5–15 минут часто достаточно.
  • Количество полей store — каждое поле добавляет немного памяти на запись. Не храните всё подряд.

Лучше несколько специализированных таблиц для разных задач (rate по IP, отдельный банлист, отдельная статистика ошибок), чем одна универсальная помойка с десятком полей.

Отладка stick-tables: CLI и stats

Без отладки rate limit превращается в магию. В HAProxy есть удобные инструменты для просмотра содержимого таблиц и их состояния.

Подключение к runtime socket

Сначала включаем сокет в конфиге:

global
  stats socket /run/haproxy/admin.sock mode 600 level admin

Смотрим содержимое таблицы:

echo "show table api_ratelimit" | socat stdio /run/haproxy/admin.sock

Или конкретный ключ:

echo "show table api_ratelimit key 192.0.2.10" | socat stdio /run/haproxy/admin.sock

Так можно увидеть текущий http_req_rate, время до истечения записи, флаги и т. д.

Логи и корреляция

Полезно логировать текущий rate в access-лог, чтобы проще отлаживать пороги. Пример лог-формата:

frontend api_frontend
  log-format "%ci:%cp [%t] %ft %b %s %TR/%Tw/%Tc/%Tr/%Tt %ST %B %tsc scur=%scur rate=%[sc0_http_req_rate(10s)] %r"

Это позволяет быстро увидеть, какие клиенты чаще всего упираются в лимиты и что происходило с rate перед ответом 429.

Типичные ошибки и подводные камни

Ошибочный type в stick-table

Если вы используете track-sc0 src, в таблице должен быть type ip. С type string такой track-sc работать не будет. Аналогично, если отслеживаете var(txn.apikey), в таблице должен быть type string.

Слишком жёсткие лимиты

Ставить лимит «с потолка» на прод — верный способ получить случайные 429 для легитимных клиентов. Лучше действовать по шагам:

  • Сначала логировать и только помечать тех, кто превышает границы (например, через дополнительный заголовок или поле в логах).
  • Посмотреть по логам, сколько реальных клиентов попадает под пороги.
  • Только потом включать deny и постепенно ужесточать правила.

Одинаковый лимит для всех эндпоинтов

Часто разные части API имеют разный профиль нагрузки. Без дифференциации вы можете:

  • несправедливо душить дешёвые кешируемые GET;
  • одновременно недолимитить тяжёлые POST.

Минимальный апгрейд — разделить лимиты хотя бы по основным путям (/auth, публичные, админка) или по методам (GET отдельно от POST).

Пошаговый чек-лист внедрения rate limit на API в HAProxy

  1. Собрать статистику текущего трафика: RPS по IP, эндпоинтам, методам и статусам.
  2. Выделить критичные эндпоинты: логин, тяжёлые POST, публичные ресурсы.
  3. Спроектировать схемы ключей: IP, ключ API, комбинированные варианты.
  4. Создать одну-две stick-table для пилота, включить только логирование без deny.
  5. Наблюдать несколько дней, корректировать пороги и окна.
  6. Включить deny 429, но сначала только для самых рискованных путей (логин, админка).
  7. Подключить мониторинг по метрикам: число 429, загрузка бэкендов, размер таблиц, частота заполнения до size.

Заключение

Stick-tables в HAProxy — это не только про sticky-сессии. Для API это мощный встроенный механизм rate limit и простая поведенческая защита от шумных клиентов, брутфорса и части HTTP-слоя DDoS.

Ключ к успеху — аккуратно спроектированные ключи (IP, токены, комбинированные варианты), разумные размеры и expire таблиц, а также обязательная отладка и мониторинг через runtime socket и логи. Так вы разгружаете backend-сервисы, снижаете риск «само-DDoS» из-за багов в клиентах и повышаете надёжность API без ввода дополнительных тяжёлых компонентов в инфраструктуру.

Если вы только планируете выносить API за балансировщик, имеет смысл сразу подобрать тариф на виртуальном хостинге или VDS с достаточным запасом по памяти под кэш stick-tables и логи — это упростит масштабирование и последующую донастройку rate limit.

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

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

cron healthchecks на VDS: контроль фоновых задач и защита от дабл-старта OpenAI Статья написана AI (GPT 5)

cron healthchecks на VDS: контроль фоновых задач и защита от дабл-старта

Регулярные задачи на VDS часто живут своей жизнью: падают молча, зависают, стартуют в двух экземплярах и конфликтуют за ресурсы. Р ...
HTTP end-to-end tracing: X-Request-ID, W3C Trace Context и заголовки OpenTelemetry OpenAI Статья написана AI (GPT 5)

HTTP end-to-end tracing: X-Request-ID, W3C Trace Context и заголовки OpenTelemetry

Когда микросервисов становится десяток и больше, а запросы проходят через несколько gateway, очередей и фоновых воркеров, простого ...
S3 и CDN для WordPress и Laravel: offload медиа и статики без боли OpenAI Статья написана AI (GPT 5)

S3 и CDN для WordPress и Laravel: offload медиа и статики без боли

Разбираем, как вынести медиа и статические файлы WordPress и Laravel в S3‑совместимый object storage и повесить сверху CDN. Пошаго ...