Настройка rate limiting для сайта: защита от DDoS 2026

Rate limiting — одна из тех вещей, которую откладывают на потом, пока сайт не ляжет под атакой в пятницу вечером. Я через это прошёл сам и видел это у десятков клиентов — поэтому сегодня разберём настройку ограничения запросов детально, с реальными конфигами и без воды.

Что такое rate limiting и почему это не опционально в 2026

Грубо говоря, rate limiting — это механизм, который ограничивает количество запросов от одного источника за единицу времени. Звучит просто. На деле это многоуровневая система, которую нужно правильно настроить на каждом слое стека: Nginx, PHP, приложение, база данных.

Ландшафт атак в 2026 году изменился кардинально. Ботнеты стали умнее: они имитируют поведение реальных пользователей, распределяют нагрузку по тысячам IP-адресов, меняют User-Agent на лету. Классический DDoS уровня L3/L4 давно перестал быть единственной угрозой. Сейчас куда опаснее L7-атаки — HTTP-флуд, брутфорс форм авторизации, credential stuffing, скрейпинг. И всё это лечится именно правильно настроенным rate limiting.

У меня был клиент — интернет-магазин на Битрикс, MySQL 8.0, примерно 3000 уникальных в сутки. В ноябре 2024 года к ним пришёл HTTP-флуд: 40 000 запросов в минуту на страницу каталога. Сервер лёг за 4 минуты. Хостинг просто отключил сайт. После того как мы настроили rate limiting на уровне Nginx + fail2ban, следующая аналогичная атака была отбита автоматически — сервер даже не почувствовал. Вот зачем это нужно.

⚠️
Важно: Rate limiting — это не замена файрволу и не серебряная пуля. Это один уровень защиты из нескольких. Читай в связке с настройкой Firewall и общими правилами защиты сайта.

Rate limiting в Nginx: базовая и продвинутая настройка

Nginx — первая линия обороны. Именно здесь нужно отсекать большинство нежелательного трафика ещё до того, как запрос дойдёт до PHP или приложения. Nginx умеет ограничивать запросы через директиву limit_req и limit_conn. Это разные вещи: первая ограничивает частоту запросов, вторая — количество одновременных соединений.

Базовая конфигурация для большинства сайтов выглядит так. Сначала объявляем зоны в блоке http, потом применяем их в location. Я обычно делаю несколько зон под разные сценарии: общий трафик, страницы авторизации, API-эндпоинты.

http {
    # Зона для общего трафика: 10 запросов в секунду с одного IP
    # Буфер на 10MB (~160 000 IP-адресов)
    limit_req_zone $binary_remote_addr zone=general:10m rate=10r/s;

    # Жёсткий лимит для страниц авторизации: 5 запросов в минуту
    limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

    # Для API: 30 запросов в секунду
    limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;

    # Лимит соединений: не более 20 одновременных с одного IP
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    server {
        listen 443 ssl http2;
        server_name example.com;

        # Применяем общий лимит
        limit_req zone=general burst=20 nodelay;
        limit_conn conn_limit 20;

        # Возвращаем 429 вместо 503 при превышении лимита
        limit_req_status 429;
        limit_conn_status 429;

        location /login {
            limit_req zone=login burst=3 nodelay;
            limit_req_status 429;
            # ... остальная конфигурация
        }

        location /api/ {
            limit_req zone=api burst=50 nodelay;
            limit_req_status 429;
            # ... остальная конфигурация
        }

        location /admin/ {
            # Для админки — максимально жёстко
            limit_req zone=login burst=2 nodelay;
            # Плюс ограничение по IP, если нужно
            allow 192.168.1.0/24;
            allow 203.0.113.10; # ваш офисный IP
            deny all;
        }
    }
}

Параметр burst — это допустимый "всплеск" запросов сверх лимита. Если rate=10r/s и burst=20, то пользователь может одномоментно послать 20 запросов, но потом они будут обрабатываться строго по 10 в секунду. nodelay означает, что запросы в рамках burst обрабатываются немедленно, а не ставятся в очередь. Для большинства сайтов — правильный выбор.

Обратите внимание на статус 429 (Too Many Requests) вместо дефолтного 503. Это семантически правильнее и позволяет клиентам (в том числе легитимным API-клиентам) понять, что нужно просто подождать, а не что сервер упал.

Защита форм авторизации и брутфорс

Брутфорс форм авторизации — это, честно говоря, самая частая атака на коммерческие сайты. Боты перебирают пароли методично, иногда по 500-1000 попыток в час с одного IP. Nginx-лимит помогает, но недостаточен без дополнительных слоёв.

На уровне PHP я всегда добавляю счётчик неудачных попыток через Redis. Это работает быстрее, чем писать в MySQL, и не создаёт лишней нагрузки на базу. Вот упрощённая реализация для Laravel (PHP 8.2+), но принцип одинаков для любого фреймворка:

ip();
        $key = 'login_attempts:' . $ip;

        $attempts = (int) Redis::get($key);

        if ($attempts >= self::MAX_ATTEMPTS) {
            $ttl = Redis::ttl($key);
            return response()->json([
                'error' => 'Too many login attempts',
                'retry_after' => $ttl,
            ], 429)->withHeaders([
                'Retry-After' => $ttl,
                'X-RateLimit-Limit' => self::MAX_ATTEMPTS,
                'X-RateLimit-Remaining' => 0,
            ]);
        }

        $response = $next($request);

        // Если авторизация провалилась — инкрементируем счётчик
        if ($response->getStatusCode() === 401 || 
            ($response->getStatusCode() === 422 && $request->routeIs('login'))) {
            
            Redis::multi();
            Redis::incr($key);
            if ($attempts === 0) {
                // Устанавливаем TTL только при первой попытке
                Redis::expire($key, self::DECAY_SECONDS);
            }
            Redis::exec();
        }

        return $response;
    }
}

На WordPress я использую плагин WP Cerber Security или Wordfence — они делают примерно то же самое, но через собственный механизм. Для Битрикс есть встроенный модуль "Проактивная защита", но его настроек по умолчанию недостаточно — нужно ужесточать вручную. Подробнее об этом можно почитать в разделе поддержки Битрикс.

💡
Совет из практики: Для форм авторизации всегда добавляй заголовок Retry-After в ответ 429. Это важно для двух вещей: легитимные API-клиенты могут корректно обработать лимит, а браузеры — показать пользователю понятное сообщение.

Fail2ban: автоматический бан по логам Nginx

Nginx ограничивает запросы в реальном времени, но не блокирует IP на уровне файрвола. Fail2ban читает логи и добавляет правила в iptables/nftables, полностью отрезая злоумышленников. Связка Nginx + Fail2ban — это классика, которая работает.

Первым делом настраиваем Nginx на логирование запросов, которые превысили лимит. По умолчанию он пишет в error.log с уровнем warn. Создаём отдельный фильтр для Fail2ban:

# /etc/fail2ban/filter.d/nginx-req-limit.conf
[Definition]
failregex = limiting requests, excess:.* by zone .*, client: 

ignoreregex =

# /etc/fail2ban/jail.local
[nginx-req-limit]
enabled  = true
filter   = nginx-req-limit
action   = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath  = /var/log/nginx/error.log
findtime = 600
maxretry = 10
bantime  = 7200

# Дополнительно — защита от брутфорса по access.log
[nginx-login-bruteforce]
enabled  = true
filter   = nginx-login-bruteforce
action   = iptables-multiport[name=LoginBrute, port="http,https", protocol=tcp]
logpath  = /var/log/nginx/access.log
findtime = 300
maxretry = 5
bantime  = 86400

# /etc/fail2ban/filter.d/nginx-login-bruteforce.conf
[Definition]
failregex = ^ -.*"(GET|POST) /login.* HTTP.*" 429

ignoreregex =

Параметр bantime = 86400 — это 24 часа бана. Для брутфорса авторизации это адекватно. Для общего rate limiting я ставлю 7200 секунд (2 часа). Если атака продолжается с тех же IP после разбана — стоит переходить на постоянный бан или использовать bantime.increment = true в новых версиях Fail2ban.

Честно говоря, Fail2ban — немного устаревшее решение. На серверах с высокой нагрузкой я перехожу на nftables с динамическими наборами IP-адресов или на более современные инструменты вроде CrowdSec. Но для большинства проектов Fail2ban работает отлично и настраивается за час.

Rate limiting на уровне CDN: Cloudflare и аналоги

Если сайт стоит за CDN — и в 2026 году это должно быть у всех коммерческих проектов — то первый рубеж защиты находится именно там. Cloudflare, Fastly, AWS CloudFront, Akamai — все они предоставляют встроенные механизмы rate limiting.

В Cloudflare (Free и Pro план) rate limiting настраивается в разделе Security → WAF → Rate Limiting Rules. Я обычно настраиваю несколько правил:

Cloudflare работает до того, как трафик вообще дойдёт до вашего сервера. Это принципиально важно при объёмных DDoS-атаках: ваш сервер просто не видит мусорный трафик. Минус — на Free-плане возможности ограничены, нормальный rate limiting требует минимум Pro ($20/месяц).

Важный момент: если сайт стоит за Cloudflare, в Nginx нужно использовать реальный IP клиента, а не IP прокси Cloudflare. Для этого нужен модуль ngx_http_realip_module и список IP-адресов Cloudflare. Иначе rate limiting в Nginx будет работать некорректно — будет банить сами серверы Cloudflare.

# В блоке http{} nginx.conf
# Реальные IP Cloudflare (обновляется, проверяй актуальный список на cloudflare.com/ips/)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
# IPv6
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;

real_ip_header CF-Connecting-IP;

Это критически важный момент, который часто упускают. Без этой конфигурации все ваши лимиты в Nginx будут применяться к IP-адресам серверов Cloudflare, а не к реальным клиентам. По теме настройки Nginx как прокси советую почитать отдельную статью — настройка обратного прокси Nginx.

Rate limiting для API: токены, пользователи, эндпоинты

API — отдельная история. Здесь rate limiting должен быть гранулярным: разные лимиты для разных ролей пользователей, разных эндпоинтов, разных методов HTTP. Банить по IP для API — плохая идея, потому что за одним корпоративным NAT могут сидеть сотни пользователей.

Правильная стратегия для API-rate-limiting: ограничивать по API-токену или по идентификатору пользователя, а не по IP. Анонимные запросы — по IP с жёстким лимитом. Авторизованные — по токену с более мягким лимитом, дифференцированным по тарифному плану.

В Laravel это делается через ThrottleRequests middleware с именованными лимитерами. В Laravel 10/11 это выглядит так:

user()) {
        $limits = [
            'free'    => 100,   // 100 запросов в минуту
            'basic'   => 500,   // 500 запросов в минуту
            'premium' => 2000,  // 2000 запросов в минуту
        ];
        
        $plan = $request->user()->subscription_plan ?? 'free';
        $limit = $limits[$plan] ?? 100;
        
        return Limit::perMinute($limit)
            ->by($request->user()->id)
            ->response(function () use ($limit) {
                return response()->json([
                    'error' => 'Rate limit exceeded',
                    'message' => 'Too many requests. Please slow down.',
                ], 429)->withHeaders([
                    'X-RateLimit-Limit' => $limit,
                    'X-RateLimit-Remaining' => 0,
                ]);
            });
    }

    // Анонимные запросы: жёсткий лимит по IP
    return Limit::perMinute(30)
        ->by($request->ip())
        ->response(function () {
            return response()->json([
                'error' => 'Rate limit exceeded',
                'message' => 'Please authenticate for higher limits.',
            ], 429);
        });
});

// Специальный лимитер для тяжёлых эндпоинтов (поиск, экспорт)
RateLimiter::for('heavy-endpoints', function (Request $request) {
    return $request->user()
        ? Limit::perMinute(10)->by($request->user()->id)
        : Limit::perMinute(3)->by($request->ip());
});

Не забывай добавлять заголовки X-RateLimit-Limit, X-RateLimit-Remaining и X-RateLimit-Reset в каждый ответ API, а не только при превышении лимита. Это хороший тон и стандарт де-факто для REST API. Клиенты смогут адаптировать свои запросы до того, как получат 429.

ℹ️
К слову о безопасности: Rate limiting — часть комплексной защиты. Не забывай про двухфакторную аутентификацию для критичных разделов и про Content Security Policy для защиты от XSS. Эти меры работают в связке.

Мониторинг и тонкая настройка лимитов

Одна из самых частых ошибок при внедрении rate limiting — поставить слишком агрессивные лимиты и начать банить легитимных пользователей. У меня был случай: настроили лимит 10 запросов в секунду на интернет-магазине, и через день начались жалобы от пользователей с быстрым интернетом — у них страница каталога с AJAX-пагинацией генерировала 15-20 запросов при быстром скроллинге. Пришлось поднять лимит до 30 и добавить burst=50.

Перед тем как включать rate limiting в production, я делаю так: неделю пишу все лимиты в режиме "только логировать" (в Nginx это директива limit_req_log_level), смотрю реальное распределение запросов по IP, нахожу 99-й перцентиль нормального трафика и ставлю лимит на 150-200% от этого значения.

Для мониторинга количества срабатываний rate limiting в Nginx удобно использовать такой запрос к логам:

# Количество 429 ответов за последний час
awk -v d="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" \
  '$0 ~ d && $9 == "429" {count++} END {print count " rate-limited requests"}' \
  /var/log/nginx/access.log

# Топ-20 IP по количеству заблокированных запросов
grep " 429 " /var/log/nginx/access.log | \
  awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# Мониторинг через GoAccess в реальном времени
goaccess /var/log/nginx/access.log \
  --log-format=COMBINED \
  --real-time-html \
  -o /var/www/html/report.html

Также советую настроить алерты в системе мониторинга. Если количество 429-ответов за минуту превысило, скажем, 1000 — это сигнал, что идёт атака или что-то сломалось у легитимных клиентов. Подробнее о настройке мониторинга в целом — в статье про мониторинг сайта.

Типичные ошибки при настройке rate limiting

За годы работы я собрал список граблей, на которые наступают снова и снова. Пройдёмся по основным.

Лимит по IP без учёта Cloudflare/прокси. Уже говорил выше, но повторю: если не настроить real_ip_header, весь трафик через CDN будет идти с одного IP. Ваш rate limiting будет банить CDN, а не атакующих.

Одинаковые лимиты для всех эндпоинтов. Статическая картинка и форма авторизации не могут иметь одинаковый rate limit. Статику вообще не нужно ограничивать rate limiting — её должен отдавать Nginx напрямую или CDN. Авторизацию — жёстко. API поиска — умеренно.

Отсутствие whitelist для мониторинга и CI/CD. Если у вас есть системы мониторинга (Zabbix, Prometheus, UptimeRobot), они регулярно стучатся на сайт. Их IP должны быть в whitelist, иначе получите ложные алерты о недоступности сайта — сам мониторинг будет заблокирован.

Не настроена страница ошибки 429. По умолчанию Nginx отдаёт стандартную страницу. Это нормально для API, но для обычных пользователей лучше настроить кастомную страницу с объяснением и таймером обратного отсчёта.

Слишком короткий bantime в Fail2ban. Бан на 10 минут — это ничто для серьёзного атакующего. Я ставлю минимум 2 часа для rate limiting и 24 часа для брутфорса авторизации. С опцией bantime.increment = true каждый следующий бан в 2 раза длиннее предыдущего.

Rate limiting без логирования. Если не логировать срабатывания, вы никогда не узнаете, была ли атака, насколько она была серьёзной и не банили ли легитимных пользователей. Логи — обязательны.

Специфика rate limiting для WordPress и Битрикс

WordPress — любимая цель атакующих именно из-за предсказуемой структуры. /wp-login.php, /xmlrpc.php, /wp-json/ — каждый знает эти пути. Для WordPress я всегда делаю три вещи помимо общего rate limiting:

Первое — блокирую /xmlrpc.php полностью, если он не нужен. Это устаревший интерфейс, который используется в 99% случаев только для атак. Второе — ставлю жёсткий лимит на /wp-login.php: 3 запроса в минуту, бан на 24 часа при превышении. Третье — ограничиваю REST API: /wp-json/ часто используется для скрейпинга контента и перебора пользователей через эндпоинт /wp-json/wp/v2/users.

Для Битрикс аналогично: /bitrix/admin/ должен быть максимально защищён — лучше вообще закрыть по IP, а публичная часть требует умеренных лимитов с учётом кеширования. Важный момент: Битрикс активно использует AJAX для обновления компонентов, так что лимит должен быть достаточно мягким для страниц с динамическим контентом. По поводу настройки кеширования в Битрикс — там тоже есть своя специфика, подробно разобрана в отдельной статье.

Если нужна помощь с настройкой защиты для конкретной CMS — это часть стандартной поддержки WordPress или поддержки Битрикс в моей практике. Rate limiting настраиваю как часть общего аудита безопасности.

Как протестировать rate limiting перед запуском

Прежде чем включать лимиты на боевом сервере, их нужно проверить. Я использую несколько инструментов: wrk, ab (Apache Benchmark) и vegeta. Последний мне нравится больше всего — гибкий, умеет задавать точную частоту запросов.

# Тест с помощью Apache Benchmark
# 1000 запросов, 50 одновременных соединений
ab -n 1000 -c 50 https://example.com/login

# С помощью wrk: 10 секунд, 4 потока, 100 соединений
wrk -t4 -c100 -d10s https://example.com/

# С помощью vegeta: точно 20 запросов в секунду в течение 30 секунд
echo "GET https://example.com/" | \
  vegeta attack -rate=20/s -duration=30s | \
  vegeta report

# Смотрим на статистику ответов: должны видеть 429 при превышении лимита
echo "GET https://example.com/login" | \
  vegeta attack -rate=10/s -duration=60s | \
  vegeta report -type=text

После теста обязательно проверяю: все ли 429 корректно логируются, сработал ли Fail2ban, не попал ли мой тестовый IP в постоянный бан (для тестов лучше использовать отдельный IP или добавить тестовый IP в whitelist Fail2ban).

И ещё один момент, который часто забывают: тестируй не только "плохой" трафик, но и то, что легитимные пользователи не попадают под ограничения. Открой сайт в браузере, быстро перейди по нескольким страницам, заполни форму — всё должно работать без ошибок 429. Если нет — лимиты слишком агрессивные.

Rate limiting — это живая конфигурация, которую нужно периодически пересматривать. Трафик растёт, паттерны атак меняются, функционал сайта расширяется. Я пересматриваю настройки лимитов раз в квартал или после любого значимого изменения в архитектуре сайта. Это не разовая настройка, а часть постоянного процесса поддержки и безопасности — как, впрочем, и регулярные бэкапы, без которых любая защита неполна.

Хотите защитить сайт от DDoS-атак и перегрузок?

Наши специалисты настроят rate limiting и комплексную защиту вашего сайта под ключ.

П
Павел
Веб-разработчик · 10+ лет опыта · Bitrix, WordPress, Laravel

Читайте также

Выбор CMS для интернет-магазина: сравнение Настройка HTTP/2 и HTTP/3 для ускорения сайта в 2026 Как настроить многоязычный сайт: полное руководство 2026