Решение проблем

Ошибка 429 Too Many Requests: что значит и как исправить в nginx, API и ботах

Ошибка 429 (HTTP 429 Too Many Requests) значит, что сработал лимит запросов. Как найти, чей он, настроить nginx limit_req и научить скрипт ждать Retry-After.

Опубликовано ~11 мин чтения
На этой странице

Коротко: ошибка 429 (HTTP 429 Too Many Requests) значит, что сработал лимит запросов. Сервер посчитал ваши запросы за определённое окно времени и отклоняет остальные, пока окно не освободится, так что это намеренный отказ, а не сбой. Если сервер ваш, найдите лимит, который сработал (limit_req в nginx, ваше приложение или Cloudflare), и настройте его так, чтобы живые пользователи проходили. Если 429 получает ваш скрипт или бот от чужого API, сбавьте темп и подождите столько, сколько велит ответ, обычно в заголовке Retry-After.

сначала: чей это лимит?
curl -s -D - -o /dev/null https://example.com/api/items
# смотрите на эти заголовки ответа:
#   server:          nginx, cloudflare или собственное имя API
#   retry-after:     сколько секунд (или до какой даты) ждать перед следующей попыткой
#   x-ratelimit-*:   собственные счётчики API (Discord, GitHub и многие другие)

Что значит ошибка 429 Too Many Requests?

Код определён в RFC 6585. Ответ должен объяснять ситуацию и может содержать заголовок Retry-After, но отправлять его сервер не обязан, а кэшам запрещено сохранять ответ 429. Как считать запросы, спецификация оставляет серверу: по IP-адресу, по API-ключу, по аккаунту, по маршруту или по нескольким признакам сразу. Поэтому один клиент может одновременно укладываться в один лимит и превышать другой.

Путаницу вносят два варианта. Собственный ограничитель nginx по умолчанию отвечает 503, а не 429, если не задать limit_req_status 429, так что «503 Service Unavailable» под нагрузкой может оказаться тем же лимитом. А некоторые API, в том числе GitHub, отвечают на превышение лимита кодом 403 или 429 и сообщают об этом в заголовке x-ratelimit-remaining: 0.

Привычное название «ошибка сервера 429» не совсем точное: код относится к классу 4xx, а это ошибки клиента. Сервер исправен и отказывает сознательно, поэтому исправлять нужно либо поведение клиента, либо настройку лимита.

ИсточникКак узнатьГде смотреть
nginx limit_req / limit_connНебольшая HTML-страница, Server: nginx (или 503, если не задан limit_req_status)limiting requests или limiting connections в журнале ошибок nginx
Собственный ограничитель приложенияJSON или страница в стиле приложения; журнал ошибок nginx молчитЛог приложения
Правило rate limiting в CloudflareСтраница Cloudflare: Error 1015, You are being rate limitedЖурнал событий безопасности в панели Cloudflare
Чужой APIЗаголовки x-ratelimit-*, поле retry_after в теле JSONЛог вашего скрипта или бота

Ошибка 429 на сайте или на YouTube: что делать пользователю

Если ошибку 429 видите вы как посетитель, ничего не сломано: лимит насчитал слишком много запросов с вашего IP-адреса или аккаунта за короткое время. Подождите несколько минут и повторите. Если ошибка возвращается, источник запросов на вашей стороне: скрипт, расширение браузера, менеджер загрузок или несколько вкладок, которые постоянно обновляют один и тот же сайт. Отключите лишнее: когда окно лимита истечёт, доступ вернётся сам.

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

Это ваш nginx? Проверьте журнал ошибок

nginx по умолчанию записывает каждый отклонённый запрос в журнал на уровне error и называет зону, которая его отклонила:

Debian · Ubuntu
sudo grep 'limiting requests' /var/log/nginx/error.log | tail -n 5

# типичная строка:
# [error] 2216#2216: *33 limiting requests, excess: 4.964 by zone "api",
#   client: 203.0.113.50, server: example.com, request: "GET /api/items HTTP/1.1"

Имя зоны говорит, какая limit_req_zone сработала; excess показывает, насколько клиент превысил настроенную скорость в момент отказа. Затем посмотрите, кто получает 429 и на каких URL. В стандартном формате лога combined статус стоит в девятом поле:

кто попадает под лимит
sudo awk '$9 == 429 {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
sudo awk '$9 == 429 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

Если в верхних строках несколько адресов Cloudflare или балансировщика нагрузки, переходите к шагу 2 ниже. Если это ваш мониторинг, вебхуки платёжного провайдера или ваш офис, смотрите шаг 3.

Как исправить ошибку 429 от nginx limit_req?

Причины, начиная с самых частых:

  1. 1Добавьте burst: лимит без него отклоняет обычную загрузку страниц

    nginx отслеживает запросы с точностью до миллисекунды, поэтому rate=10r/s на деле означает один запрос каждые 100 мс. Браузер при загрузке одной страницы отправляет десяток запросов сразу, и без burst каждый, кроме первого, в этом окне в 100 мс отклоняется. Три одновременных запроса к зоне 10r/s без burst получат ответы 200, 429, 429, если задан limit_req_status 429 (как ниже), а без этой строки 200, 503, 503.

    /etc/nginx/conf.d/ratelimit.conf (контекст http)
    limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
    limit_req_status 429;      # по умолчанию 503
    limit_conn_status 429;     # то же для limit_conn, если вы его используете
    в вашем блоке server
    location /api/ {
        limit_req zone=perip burst=20 nodelay;
        proxy_pass http://127.0.0.1:3000;
    }

    burst=20 позволяет клиенту превысить скорость на 20 запросов, прежде чем nginx начнёт отказывать, а свободные места восполняются с настроенной скоростью. С nodelay эти лишние запросы обслуживаются сразу; без него nginx ставит их в очередь и выпускает с заданной скоростью, так что посетители видят медленные ответы вместо ошибок. delay=8 совмещает оба режима: первые 8 лишних запросов проходят сразу, остальные растягиваются во времени. Зоны в 10 МБ хватает большинству сайтов: на 64-битных системах один мегабайт вмещает около 8 000 состояний клиентов.

  2. 2Убедитесь, что nginx видит настоящий IP клиента

    За Cloudflare, балансировщиком или любым другим прокси $binary_remote_addr — это адрес прокси, поэтому все ваши посетители делят несколько общих счётчиков и упираются в лимит вместе. Верните IP посетителя модулем realip:

    /etc/nginx/conf.d/realip.conf
    set_real_ip_from 173.245.48.0/20;
    # ... по строке на каждый диапазон с https://www.cloudflare.com/ips/
    real_ip_header CF-Connecting-IP;

    Указывайте только адреса, через которые трафик действительно приходит, иначе кто угодно подделает заголовок и обойдёт лимит. Полная настройка Cloudflare описана в руководстве по обратному прокси nginx (англ.). Офисы и мобильные операторы за NAT операторского уровня (CGNAT) тоже вполне законно делят один адрес, поэтому на публичных страницах держите лимиты на IP щедрыми, а строгими делайте только на форме входа.

  3. 3Ограничивайте только то, что нужно, и исключите свои инструменты

    limit_req на уровне server покрывает и картинки, CSS, проверки работоспособности, мониторинг доступности и вебхуки. Ставьте строгие лимиты на вход, поиск и маршруты API, а статические файлы пропускайте. Чтобы исключить известные адреса, воспользуйтесь тем, что nginx не считает запросы с пустым ключом:

    /etc/nginx/conf.d/ratelimit.conf (контекст http)
    geo $rate_exempt {
        default        0;
        198.51.100.10  1;    # ваш мониторинг
        10.0.0.0/8     1;    # внутренняя сеть
    }
    map $rate_exempt $rate_key {
        0  $binary_remote_addr;
        1  "";               # пустой ключ: запрос не считается
    }
    limit_req_zone $rate_key zone=perip:10m rate=10r/s;

    Одно правило наследования часто подводит: location наследует limit_req из блока server, только если в нём нет собственного limit_req. Добавьте его в location, и лимиты уровня server там больше не действуют.

  4. 4Сначала измерьте, потом включайте

    Режим dry run считает и пишет в лог, ничего не отклоняя, а переменная $limit_req_status показывает, что произошло бы. Оба появились в nginx 1.17 в 2019 году, так что они есть в любом актуальном пакете Debian и Ubuntu:

    пробный запуск на сутки, затем подсчёт
    # контекст http
    log_format ratelog '$remote_addr [$time_local] "$request" $status $limit_req_status';
    
    # в location, который проверяете
    limit_req zone=perip burst=20 nodelay;
    limit_req_dry_run on;
    # access_log здесь заменяет унаследованный, поэтому укажите и обычный лог
    access_log /var/log/nginx/access.log;
    access_log /var/log/nginx/ratelimit.log ratelog;
    
    # позже:
    grep -c REJECTED_DRY_RUN /var/log/nginx/ratelimit.log
  5. 5Скажите клиентам, сколько ждать

    Сам nginx заголовок Retry-After не отправляет. Направьте 429 в именованный location, который его добавит, и аккуратные клиенты будут ждать нужное время, а не заваливать сервер повторами:

    в вашем блоке server
    location /api/ {
        limit_req zone=perip burst=20 nodelay;
        error_page 429 @ratelimited;
        proxy_pass http://127.0.0.1:3000;
    }
    
    location @ratelimited {
        add_header Retry-After 10 always;
        default_type application/json;
        return 429 '{"error":"rate_limited","retry_after":10}';
    }
    применить
    sudo nginx -t && sudo systemctl reload nginx

Что, если журнал ошибок nginx молчит?

Значит, 429 приходит откуда-то ещё. Если тело ответа — JSON вашего приложения, сработал его собственный ограничитель, и проблема с IP из шага 2 повторяется уровнем ниже. Express, например, видит все запросы как пришедшие от nginx, пока вы не зададите app.set('trust proxy', 1), а для этого nginx должен передавать X-Forwarded-For.

Если Cloudflare показывает страницу Error 1015, запрос заблокировало правило rate limiting в панели Cloudflare ещё до того, как он дошёл до вашего сервера, поэтому в ваших логах пусто. Такие правила по умолчанию отвечают 429, а задать можно любой код от 400 до 499. Если на правило натыкаются живые посетители, поднимите допустимое число запросов или удлините период подсчёта, а не удаляйте правило: собственная страница Cloudflare об ошибке 1015 предупреждает, что односекундные окна слишком строгие, и советует окна от 10 секунд.

Получаете 429 от чужого API? Повторяйте запросы правильно

  • Сразу остановитесь. Запросы, отправленные, пока действует ограничение, всё равно отклоняются, а некоторые API ещё и засчитывают их вам в минус. Discord считает большинство ответов 429 недействительными запросами.
  • Ждите столько, сколько говорит сервер. Retry-After содержит либо число секунд, либо HTTP-дату. Многие API добавляют свою подсказку: retry_after в теле JSON у Discord, parameters.retry_after у Telegram, метку времени x-ratelimit-reset у GitHub.
  • Подсказки нет? Экспоненциальная задержка со случайным разбросом. Ждите случайное время от нуля до base × 2^attempt с верхним пределом. Разброс (jitter) не даёт множеству клиентов повторять запросы синхронно и снова вместе упираться в лимит.
  • Ограничьте число повторов и после последнего завершайте работу с явной ошибкой, чтобы зависшая задача не крутилась вечно.
curl
# повторяет при тайм-аутах и кодах 408, 429, 500, 502, 503, 504 (новый curl также 522, 524)
# до 5 раз, удваивая паузу с 1 с, и учитывает Retry-After (curl 7.66.0 и новее)
curl --retry 5 --retry-max-time 120 -sS https://api.example.com/items
Python · requests
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

retry = Retry(
    total=5,
    status_forcelist=[429, 503],
    backoff_factor=1,                 # 0 с, 2 с, 4 с, 8 с ... если Retry-After не прислан
    respect_retry_after_header=True,  # значение по умолчанию, указано для наглядности
)
session = requests.Session()
session.mount("https://", HTTPAdapter(max_retries=retry))

resp = session.get("https://api.example.com/items", timeout=30)

urllib3 по умолчанию не повторяет POST, и это правильно: повтор POST может повторить и его действие, если API на это не рассчитан. Когда попытки заканчиваются, requests выбрасывает RetryError. Написать цикл самому тоже несложно:

Python · full jitter вручную
import random, time
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
import requests

def retry_after_seconds(resp):
    value = resp.headers.get("Retry-After")
    if value is None:
        return None
    if value.strip().isdigit():                  # "120"
        return int(value)
    try:                                         # "Wed, 21 Oct 2026 07:28:00 GMT"
        when = parsedate_to_datetime(value)
    except (TypeError, ValueError):
        return None
    return max(0.0, (when - datetime.now(timezone.utc)).total_seconds())

def get_with_backoff(url, max_tries=6, base=1.0, cap=60.0):
    for attempt in range(max_tries):
        resp = requests.get(url, timeout=30)
        if resp.status_code not in (429, 503) or attempt == max_tries - 1:
            return resp
        wait = retry_after_seconds(resp)
        if wait is None:
            wait = random.uniform(0, min(cap, base * 2 ** attempt))
        time.sleep(wait)

Почему мой бот в Discord или Telegram получает 429?

Discord ограничивает каждый маршрут отдельно, по корзинам (buckets), плюс действует общий потолок в 50 запросов в секунду на бота. В ответах есть X-RateLimit-Limit, -Remaining, -Reset, -Reset-After и -Bucket; ответ 429 добавляет Retry-After, X-RateLimit-Scope и тело JSON с retry_after в секундах (с дробной частью) и флагом global. discord.py и discord.js ставят запросы в очередь по корзинам и сами пережидают 429; discord.py при этом пишет в лог предупреждение We are being rate limited. Видеть его время от времени нормально. Если оно идёт постоянно, ваш код воюет с лимитом:

  • Цикл, который много раз в секунду редактирует одно и то же сообщение или меняет роли и ники.
  • Массовые личные сообщения или пакетные операции с каналами без пауз.
  • Несколько процессов с одним и тем же токеном. Каждый экземпляр библиотеки учитывает только свои запросы, и вместе они переполняют корзины.
  • Бот падает и перезапускается по кругу, а при каждом старте заново регистрирует слэш-команды. Discord разрешает создать не больше 200 команд в сутки на один сервер (guild).

Telegram возвращает время ожидания в теле ответа:

Telegram Bot API · ответ 429
{"ok": false, "error_code": 429,
 "description": "Too Many Requests: retry after 35",
 "parameters": {"retry_after": 35}}

FAQ Telegram для ботов просит держаться примерно одного сообщения в секунду в одном чате, не больше 20 сообщений в минуту в группе и около 30 сообщений в секунду по всем чатам при рассылке, а большие рассылки растягивать на часы. В python-telegram-bot для этого есть необязательный ограничитель: установите python-telegram-bot[rate-limiter] и передайте AIORateLimiter() в билдер приложения.

Важно и то, где работает бот. Discord применяет блокировку за недействительные запросы к IP-адресу, поэтому на бесплатном хостинге, где много ботов делят один адрес, чужие ошибки засчитываются и вам. На небольшом VPS со своим IPv4 этот счётчик остаётся только вашим: тарифы есть на странице хостинга для Discord-ботов, а настройку через systemd разбирает руководство по хостингу бота на VPS (англ.).

Как не допустить повторения ошибки 429?

  • На своём сервере: задайте limit_req_status 429, дайте каждому лимиту burst, считайте по настоящему IP клиента, ограничивайте вход и API, а не статику, сначала проверяйте лимит в режиме dry run и отправляйте Retry-After.
  • Клиентов, которые раз за разом упираются в лимит, передавайте файрволу: включите стандартный jail nginx-limit-req в fail2ban (англ.), он читает те же строки журнала ошибок.
  • В своих клиентах: один общий клиент на токен, который учитывает лимиты, соблюдение Retry-After, задержка с разбросом, предел повторов и кэш для данных, которые вы читаете снова и снова.
  • Разделяйте сетевую защиту и защиту приложения. Постоянно включённая защита от DDoS отсеивает флуд до того, как он дойдёт до сервера, но ничего не знает о ваших лимитах на пользователя. Где кончается одна и начинается другая, объясняет статья Что покрывает защита от DDoS (англ.).

Всё описанное работает на любом KVM VPS с root-доступом и стандартным пакетом nginx в Ubuntu или Debian.

Частые вопросы

Ошибка 429: виноват я или сайт?

Обычно ничего не сломано: лимит насчитал слишком много запросов с вашего IP-адреса, аккаунта или API-ключа за короткое время и какое-то время отклоняет остальные. Подождите минуту и повторите. Если ошибка возвращается, что-то на вашей стороне отправляет запросы в цикле: скрипт, расширение браузера или несколько вкладок, которые постоянно обновляют один и тот же сайт.

Сколько ждать после ответа 429 Too Many Requests?

Столько, сколько указано в заголовке Retry-After: там либо число секунд, либо дата. Discord и Telegram дополнительно передают паузу в теле JSON в поле retry_after. Если сервер ничего не подсказывает, увеличивайте паузу экспоненциально со случайным разбросом, начиная примерно с одной секунды, и прекращайте попытки после заранее заданного их числа.

Почему nginx при ограничении запросов отвечает 503, а не 429?

limit_req и limit_conn в nginx по умолчанию отвечают 503 Service Unavailable. Добавьте limit_req_status 429; и limit_conn_status 429; в блок http, server или location и выполните sudo nginx -t && sudo systemctl reload nginx. Тогда клиенты и мониторинг смогут отличить лимит от настоящего сбоя.

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

Все статьи раздела