Rate limit и защита API от перебора — практика на nginx и PHP
Логин и API без ограничения частоты — открытая дверь для перебора паролей и парсинга. Показываем rate limit на nginx и в приложении, код 429 и как не порезать офис за одним IP.
Форма логина без ограничения частоты — это приглашение перебрать пароли: бот шлёт тысячи попыток в минуту, пока не угадает. Открытый API без лимита выкачают парсеры за ночь. Rate limit — ограничение числа запросов с одного источника за окно времени — базовая защита, которую ставят на двух уровнях: на nginx и в самом приложении.
Зачем ограничивать частоту
- Brute-force логина. Пять попыток пароля в минуту — нормально, пятьсот — атака.
- Парсинг API. Конкурент выкачивает каталог тысячами запросов подряд.
- Спам через формы. Один бот шлёт сотни заявок за минуту.
- Нагрузка. Даже кривой скрипт без злого умысла может уронить сервер.
Rate limit на nginx
Первый рубеж — до того, как запрос дойдёт до PHP. nginx отсекает лишнее дёшево, не нагружая приложение. Объявляем зону и применяем к чувствительному маршруту:
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
location /api/login {
limit_req zone=login burst=3 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
rate=5r/m — пять запросов в минуту, burst=3 — короткий всплеск сверх лимита без отказа, nodelay — не растягивать всплеск во времени. Зона на 10 МБ хранит около 160 тысяч адресов.
Лимит в приложении
nginx считает по IP и не знает про пользователей. Второй рубеж — в PHP, где можно лимитировать по логину, ключу API или сессии. Быстрый счётчик — на apcu (в памяти одного сервера) или Redis (общий на кластер):
$key = 'rl:' . $ip;
$hits = apcu_inc($key);
if ($hits === 1) {
apcu_store($key, 1, 60); // окно 60 секунд
}
if ($hits > 20) {
http_response_code(429);
header('Retry-After: 60');
exit;
}
Для нескольких серверов apcu не годится — счётчик у каждого свой. Тогда Redis с командой INCR и EXPIRE: один общий счётчик на всю ферму.
Правильный ответ: 429 и Retry-After
Не отдавайте 200 и не рвите соединение молча. Верните код 429 Too Many Requests и заголовок Retry-After с числом секунд. Тогда честный клиент или ваше же приложение поймёт, что надо подождать, а не завалит сервер повторами.
Временный бан и люди за одним NAT
При явной атаке — не просто 429, а временный бан IP на 5-15 минут. Но здесь главная ловушка: за одним внешним IP может сидеть целый офис, вуз или мобильный оператор через NAT. Забаните адрес — отрежете десятки живых людей.
- Для логина считайте по паре «IP + логин», а не по одному IP.
- Давайте адекватный
burst, чтобы офис из 30 человек не упёрся в лимит одиночки. - Белый список для своих адресов и мониторинга.
Грабли
- Слишком жёсткий лимит режет офис за общим IP — жалобы «сайт не работает».
- Лимит только на nginx — API по внутренней сети остаётся открытым.
- Бан навсегда вместо временного — забытое правило блокирует клиента месяцами.
- Нет заголовка Retry-After — клиент долбит повторами и усугубляет нагрузку.
Вывод
Rate limit ставят в два слоя — грубый на nginx и точный в приложении — и обязательно с кодом 429, Retry-After и оглядкой на людей за общим NAT. Настроить это без риска отрезать живых клиентов — тонкая работа: если нужен разбор ваших логов и аккуратная настройка лимитов, наша техническая поддержка подберёт пороги под ваш трафик.