Коротко: ошибка 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 и называет зону, которая его отклонила:
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Добавьте 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Убедитесь, что 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Ограничивайте только то, что нужно, и исключите свои инструменты
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Сначала измерьте, потом включайте
Режим 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Скажите клиентам, сколько ждать
Сам 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) не даёт множеству клиентов повторять запросы синхронно и снова вместе упираться в лимит. - Ограничьте число повторов и после последнего завершайте работу с явной ошибкой, чтобы зависшая задача не крутилась вечно.
# повторяет при тайм-аутах и кодах 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/itemsimport 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. Написать цикл самому тоже несложно:
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 возвращает время ожидания в теле ответа:
{"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. Тогда клиенты и мониторинг смогут отличить лимит от настоящего сбоя.