Rate limiting в Nginx: защита формы входа от перебора
Один пароль можно проверять тысячи раз, если оставить форму входа без ограничений. Показываем конфигурацию Nginx 1.24+, разбираем burst, 429, прокси и границы защиты от перебора.

Форма входа без ограничения скорости превращает подбор паролей в задачу на выносливость: бот отправляет запросы к одному endpoint, а приложение последовательно проверяет каждый из них. В Nginx Open Source 1.24+ это можно ограничить без изменений в коде: задать лимит на IP, короткий запас запросов и ответ 429. Но такой барьер не заменяет блокировку учётной записи и не остановит распределённый перебор с сотен адресов.
Что именно ограничивает Nginx
Модуль ngx_http_limit_req_module отслеживает частоту запросов по ключу. Для защиты формы входа обычно используют бинарное представление IP-адреса: $binary_remote_addr. Оно занимает меньше памяти, чем текстовый адрес, и подходит для общей зоны лимитов.
Схема работает на уровне HTTP-запроса. Nginx не знает, правильный ли пароль вводит пользователь, к какой учётной записи относится попытка и была ли она успешной. Поэтому один IP-лимит защищает endpoint, но не решает задачу блокировки конкретного аккаунта.
Rate limiting должен сделать перебор дорогим и медленным, а не создавать иллюзию, что одной настройкой Nginx вы закрыли всю аутентификацию.
Лимит запросов — правило, которое ограничивает скорость обращений к ресурсу. В Nginx оно реализовано по алгоритму «текущего ведра»: запросы допускаются с заданной скоростью, а краткий всплеск можно принять или поставить в очередь.
Базовая конфигурация для endpoint входа
Ниже пример для Nginx Open Source 1.24+. Поместите зону в контекст http, а правило — в нужный server или точный location. В примере форма входа находится по адресу /login, а приложение слушает upstream с именем app_backend.
http {
limit_req_zone $binary_remote_addr zone=login_per_ip:10m rate=5r/m;
upstream app_backend {
server 127.0.0.1:8000;
}
server {
listen 443 ssl;
server_name example.com;
location = /login {
limit_req zone=login_per_ip burst=2 nodelay;
limit_req_status 429;
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Здесь один ключ получает в среднем не более пяти запросов в минуту. Параметр burst=2 разрешает короткий запас из двух запросов. nodelay не ставит запасные запросы в очередь, а пропускает их сразу; это уменьшает задержку, но позволяет боту сделать небольшой мгновенный всплеск. Для формы входа это компромисс, а не универсально правильное значение.
Проверка после изменения
- Проверьте синтаксис. Выполните
sudo nginx -t. В успешном случае Nginx должен сообщить, что синтаксис конфигурации корректен и проверка прошла успешно. - Перечитайте конфигурацию. Выполните
sudo nginx -s reload. При этой операции действующие соединения не должны обрываться только из-за перечитывания настроек. - Проверьте ответ endpoint. Отправьте несколько запросов к
/loginс одного тестового IP. После превышения лимита сервер должен вернуть HTTP 429, если запросы не были приняты приложением через доступный burst. - Проверьте обычный вход. Успешная авторизация, CSS, JavaScript и запросы к другим адресам не должны случайно попадать под это правило.
Как выбрать ключ и параметры
Главная ошибка — бездумно копировать лимит из примера. Его нужно считать от реального сценария. Если пользователь может дважды отправить форму из-за медленного ответа, слишком жёсткое правило начнёт возвращать 429 легитимным клиентам. Если лимит слишком мягкий, бот почти не почувствует ограничений.
| Подход | Что ограничивает | Плюс | Цена и риск |
|---|---|---|---|
$binary_remote_addr | Все попытки с одного IP | Простая настройка, почти не требует приложения | Общий Wi‑Fi, мобильные сети и NAT могут объединить разных пользователей |
| Ключ на основе доверенного идентификатора | Запросы одного клиента или API-ключа | Точнее для авторизованных API | До входа идентификатора ещё нет; подменяемый заголовок использовать нельзя |
| Ограничение в приложении по аккаунту | Попытки для конкретной учётной записи | Защищает аккаунт даже при смене IP | Нужны хранение счётчиков, политика сброса и защита от перечисления пользователей |
| Комбинация Nginx и приложения | IP, endpoint и аккаунт одновременно | Лучше против разных сценариев перебора | Больше состояний, тестов и риска ошибочно заблокировать пользователя |
Для публичной формы входа начните с IP-лимита, но добавьте в приложение отдельное правило для аккаунта. Не сообщайте клиенту, существует ли введённый логин: одинаковое сообщение об ошибке снижает риск перечисления пользователей.
Почему не стоит сразу ставить слишком маленький лимит
Один внешний IP может обслуживать офис, университет, гостиницу или мобильного оператора. Лимит «один запрос в минуту» способен заблокировать десятки независимых пользователей. Наоборот, значение в десятки запросов в секунду оставляет перебору слишком много пространства. Начинайте с наблюдения за нормальным трафиком и проверяйте частоту неудачных входов до включения жёсткого режима.
Для диагностики можно временно использовать режим, который только считает превышения и не отклоняет запросы, но точное имя и поведение директивы сверяйте с документацией версии Nginx. Логи должны позволять связать 429 с endpoint, IP, временем и upstream, не записывая пароль или полный токен запроса.
Прокси, IPv6 и реальный IP клиента
Если Nginx стоит за CDN, балансировщиком или другим reverse proxy, переменная $remote_addr может содержать адрес самого прокси. Тогда все пользователи попадут в одну корзину и быстро начнут получать 429.
Исправление — настроить модуль realip только для списка доверенных прокси. Нельзя безусловно брать IP из любого заголовка X-Forwarded-For: клиент может прислать его сам и получить новый ключ для каждого запроса. Список доверенных сетей, название заголовка и порядок адресов зависят от вашей инфраструктуры, поэтому их нужно сверить с документацией CDN или балансировщика.
IPv4 и IPv6 также могут распределять пользователей по-разному. Включите в тесты офисную сеть, мобильный интернет и IPv6-клиента. Не маскируйте часть IPv6-адреса без отдельного обоснования: это увеличит число ложных блокировок.
Что ломается чаще всего
«unknown directive limit_req_zone»
Модуль ограничения запросов не собран или недоступен в установленной сборке. Проверьте вывод nginx -V, пакет вашей ОС и документацию сборщика. Не заменяйте директиву похожим названием: сначала подтвердите, что используется именно модуль ngx_http_limit_req_module.
«limit_req_zone directive is not allowed here»
Зона объявлена не в контексте http. Перенесите limit_req_zone в основной блок HTTP-конфигурации, а limit_req оставьте в server или location.
Пользователи получают 429 сразу после включения
Проверьте, не видит ли Nginx общий адрес CDN или NAT, не слишком ли мал rate и не применяется ли правило ко всему server вместо одного endpoint. Посмотрите access log и фактическое значение адреса клиента. Увеличивать лимит вслепую не стоит: сначала исправьте ключ.
Ограничение не останавливает распределённый перебор
IP-лимит действует на каждый ключ отдельно. Ботнет с сотнями адресов обойдёт его, даже если каждый адрес отправляет мало запросов. В этом сценарии нужны правила на уровне аккаунта, CAPTCHA или дополнительная защита на границе сети — с учётом того, что CAPTCHA тоже не должна становиться единственным фактором безопасности.
Когда Nginx — только первый слой
Nginx хорошо отсекает слишком частые обращения до приложения и экономит его CPU, базу данных и очередь рабочих процессов. Но он не видит бизнес-контекст. Для полноценной защиты приложение должно учитывать неудачные попытки по аккаунту, уведомлять о подозрительной активности и не раскрывать существование логина.
Отдельно продумайте восстановление доступа. Если жёсткая блокировка аккаунта зависит только от числа неудачных попыток, злоумышленник сможет намеренно блокировать чужие учётные записи. Часто безопаснее вводить задержку, временное усиление проверки и понятный канал разблокировки, чем навсегда отключать вход.
Что сделать дальше
Сначала включите наблюдение без жёсткого отклонения либо протестируйте правило на отдельном endpoint. Затем измерьте долю 429, количество неудачных входов на IP и аккаунт, распределение адресов за прокси и долю легитимных пользователей, попавших под ограничение. После этого добавьте серверное правило по аккаунту и проверьте сценарии NAT, IPv6, CDN и восстановления доступа. Инвалидация сессий и настройка CAPTCHA — отдельные задачи, их не стоит прятать внутри одного лимита Nginx.
Инфобез и 152-ФЗ: Аудит политик конфиденциальности, правила сбора cookie.
Спросить за 20 ₽Источники: NGINX documentation: ngx_http_limit_req_module, OWASP Authentication Cheat Sheet
Частые вопросы
Достаточно ли rate limiting в Nginx для защиты от перебора паролей?
Почему после настройки все пользователи офиса получают 429?
Что лучше для формы входа: burst с nodelay или очередь?
Можно ли брать IP из X-Forwarded-For?
Что делать, если перебор идёт с сотен IP?
Почему Nginx возвращает 429, хотя приложение работает нормально?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.