Telegram-бот на сервере: частые ошибки и решения
Бот работал на ноутбуке, переехал на сервер — и начал отвечать через раз, молчать в группе или падать посреди рассылки. Почти все ошибки Telegram-бота возвращает сам Telegram, JSON'ом с точной причиной, но фреймворк заворачивает их в исключение, и в логе остаётся хвост трейсбека. Ниже — повторяющиеся сценарии с реальными текстами ответов Bot API и командами, которые называют причину.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Диагностика: три запроса, которые называют причину
Бот — обычный HTTPS-клиент к api.telegram.org. Прежде чем читать свой код, спросите Telegram напрямую, минуя фреймворк:
TOKEN=7684512309:AAH... # без кавычек
curl -s "https://api.telegram.org/bot$TOKEN/getMe" | jq
curl -s "https://api.telegram.org/bot$TOKEN/getWebhookInfo" | jq
journalctl -u tgbot -n 200 --no-pager | grep -iE 'error|conflict|timeout'
Здоровый getMe вернёт {"ok":true,"result":{"id":7684512309,"can_read_all_group_messages":false}}. Последнее поле пригодится дальше: false — включённый privacy mode, отдельный источник жалоб «бот молчит в группе».
Дальше правило простое: "ok": false — проблема в запросе к Bot API (токен, права, лимиты); исключение без JSON (ConnectError, TimedOut) — проблема в сети, запрос до Telegram не дошёл.
409 Conflict: бот запущен дважды
Самая частая ошибка после переезда. В python-telegram-bot 21–22:
telegram.error.Conflict: Conflict: terminated by other getUpdates request;
make sure that only one bot instance is running
В aiogram 3.x это TelegramConflictError с тем же текстом. Telegram отдаёт одному токену ровно один поток getUpdates: второй процесс выбивает первый, первый переподключается и выбивает второй — оба живы, оба ругаются, пользователь видит ответ через раз. Источников второго процесса три:
- Старый процесс не умер.
ps -eo pid,etimes,cmd | grep "[b]ot.py": колонкаetimesпоказывает возраст в секундах, и если там 86400 при «только что перезапустил» — процесс переживает ваши рестарты. То же с лишнимdocker run. - Локальная отладка боевым токеном. Бот на сервере моргает ровно тогда, когда разработчик запускает копию у себя. Лечится не кодом, а отдельным тестовым ботом от BotFather.
- Webhook активен, а код зовёт getUpdates. Текст однозначный:
Conflict: can't use getUpdates method while webhook is active; use deleteWebhook to delete the webhook first. Снимается запросомdeleteWebhook?drop_pending_updates=true— но честно: параметр выбрасывает всю очередь, и сообщения за время простоя теряются безвозвратно.
Страховка — запретить второй запуск системно: ExecStart=/usr/bin/flock -n /run/tgbot.lock /opt/tgbot/.venv/bin/python bot.py. Ключ -n означает «не ждать»: занят лок — второй процесс выходит с кодом 1 и оставляет строку в журнале вместо тихой войны инстансов. Про перезапуски по кругу — бот падает после перезапуска.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть бот-стек401 и 404: что не так с токеном
Ответ лаконичен до обидного: {"ok":false,"error_code":401,"description":"Unauthorized"}. Причины по убыванию частоты:
- Кавычки из
.env. СтрокуBOT_TOKEN="7684512309:AAH..."python-dotenvразберёт правильно, аdocker run --env-fileиEnvironmentFileв systemd кавычки не снимают — символ"уезжает прямо в URL. - Windows-переводы строк. Файл правили в блокноте — в конце строки
\r. Диагностика мгновенная:cat -A /opt/tgbot/.envпокажетBOT_TOKEN=7684...^M$, лечение —sed -i 's/\r$//' /opt/tgbot/.env. - Токен отозван.
/revokeв BotFather обнуляет старый мгновенно. Если токен попадал в публичный git — отзывайте не раздумывая: с ним посторонний читает все апдейты бота.
Проверяйте не файл, а то, что видит процесс: sudo tr '\0' '\n' < /proc/$(pgrep -f bot.py)/environ | grep BOT_TOKEN.
Отдельный сюрприз — 404 с описанием Not Found, который читается как «метод не найден». Токен здесь часть пути: https://api.telegram.org/bot<TOKEN>/sendMessage. Не доехала переменная — URL превращается в .../botNone/sendMessage, и Telegram честно отвечает «такого бота нет». То есть 404 почти всегда тоже про токен.
Webhook установлен, а апдейты не приходят
Весь диагноз даёт getWebhookInfo. Вот «больной» ответ:
{"url":"https://bot.example.com/tg/8f21b0","pending_update_count":213,
"last_error_message":"Wrong response from the webhook: 502 Bad Gateway",
"max_connections":40,"ip_address":"203.0.113.10"}
Растущий pending_update_count при непустом last_error_message — копящаяся очередь; апдейты хранятся 24 часа. Расшифровка типовых сообщений:
last_error_message | Что это на самом деле |
|---|---|
SSL error {337047686, error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol} | На 443 отвечает не TLS: nginx слушает порт как обычный http |
SSL error {336134278, ... certificate verify failed} | Отдаётся cert.pem вместо fullchain.pem |
Wrong response from the webhook: 502 Bad Gateway | Nginx жив, приложение за ним — нет |
Wrong response from the webhook: 404 Not Found | Путь в setWebhook не совпадает с маршрутом в коде |
Порт. Вебхук ставится только на 80, 88, 443 или 8443; попытка повесить его на 3000 отваливается сразу: Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443.
Цепочка сертификата. Команда openssl s_client -connect bot.example.com:443 | grep 'Verify return' должна вернуть Verify return code: 0 (ok). Браузер достраивает цепочку сам, Telegram — нет: зелёный замочек ничего не доказывает.
Секрет и фильтр апдейтов. Кроме url в setWebhook стоит передать secret_token=$(openssl rand -hex 16), allowed_updates=["message","callback_query"] и max_connections=20. Секрет придёт в заголовке X-Telegram-Bot-Api-Secret-Token — сверяйте его в коде, иначе любой, кто узнал URL, шлёт вам поддельные апдейты. allowed_updates отрезает мусорный трафик, max_connections (по умолчанию 40, диапазон 1–100) держит нагрузку в рамках небольшого сервера.
Быстрый 200. Любой другой ответ Telegram считает недоставкой и повторяет апдейт — отсюда дубли у пользователей. Хендлер принимает апдейт, кладёт в очередь и сразу отвечает; в nginx хватает proxy_pass http://127.0.0.1:8080; и proxy_read_timeout 20s;. Тонкость, на которой теряют час: свои заголовки с подчёркиванием nginx молча вырезает, им нужен underscores_in_headers on;.
403 и 400 при отправке: чат, права и разметка
Рассылка на тысячу человек падает на первом же адресате, если код не ловит вот это:
{"ok":false,"error_code":403,"description":"Forbidden: bot was blocked by the user"}
Это не сбой — пользователь нажал «Заблокировать». Реакция: пометить is_active = false и идти дальше. Рядом живут Forbidden: user is deactivated и Forbidden: bot was kicked from the group chat. Дальше 400-е:
Bad Request: chat not found— бот незнаком с этимchat_id. Бот не пишет первым, диалог начинает пользователь через/start. Второй вариант — отправка по@usernameканала, где бот не администратор.Bad Request: group chat was upgraded to a supergroup chat— в теле будет"parameters":{"migrate_to_chat_id":-1001234567890}. Старыйchat_idмёртв навсегда; не обработаете — бот замолчит в чате без единой строки в логе.Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 217— незакрытая*или_приparse_mode=Markdown, обычно на пользовательских данных: фамилияIvanov_Petrovпревращает сообщение в мусор. Практичнееparse_mode=HTMLсhtml.escape().Bad Request: BUTTON_DATA_INVALID—callback_dataдлиннее 64 байт. Кладите в кнопку короткий ключ, данные — в Redis.
Отдельная история — молчание в группе: при can_read_all_group_messages: false бот видит только адресованные ему команды и реплаи. Privacy mode отключается через BotFather (/setprivacy → Disable), но к уже добавленным чатам настройка не применяется — бота надо удалить из группы и добавить заново.
429, флуд-контроль и сетевые обрывы
Ответ при превышении лимита: {"ok":false,"error_code":429,"description":"Too Many Requests: retry after 34","parameters":{"retry_after":34}}.
Ориентиры: около 30 сообщений в секунду суммарно, не быстрее одного в секунду в один чат, около 20 в минуту в одну группу — рассылка простым циклом упирается в потолок на второй сотне получателей. Ждать нужно ровно retry_after секунд: повторы во время ограничения продлевают его. И различайте два 429: retry after 3 — флуд-контроль метода, retry after 3600 — ограничение бота целиком за рассылку неподписанным.
try:
await bot.send_message(chat_id, text)
except RetryAfter as e:
await asyncio.sleep(e.retry_after + 1) # столько, сколько просят
except Forbidden:
await mark_inactive(chat_id) # заблокировал бота
await asyncio.sleep(0.04) # 25 сообщений в секунду
Теперь ошибки, где JSON не приходит вовсе. Строка telegram.error.NetworkError: httpx.ConnectError: [Errno 101] Network is unreachable в девяти случаях из десяти означает IPv6: AAAA-маршрут у сервера есть, но не работает, а резолвер отдаёт IPv6-адрес первым. Проверка — getent ahosts api.telegram.org и тот же curl с ключом --ipv4. Работает с ним и не работает без — раскомментируйте в /etc/gai.conf строку precedence ::ffff:0:0/96 100.
Второй сюжет — вечный telegram.error.TimedOut: Timed out на исправной сети при long polling. Причина арифметическая: read-timeout HTTP-клиента меньше, чем timeout у getUpdates. Просите держать соединение 30 секунд, а клиент рвёт его через 10 — лог заполняется таймаутами на ровном месте.
И стена, о которой узнают внезапно: бот отправляет файлы до 50 МБ, а скачивает через getFile только до 20 МБ, дальше Bad Request: file is too big. Лечится своим telegram-bot-api с ключом --local: лимит растёт до 2000 МБ, но файлы ложатся на ваш диск.
Какой сервер брать под бота в MAATRIX
Требования у бота скромные: узкое место почти никогда не в процессоре. Ломают ботов другие вещи — нехватка памяти, когда рядом появляется база, отсутствие внешнего IP с открытыми 80 и 443 и нестабильный канал до api.telegram.org.
| Задача | Конфигурация | Что реально получите |
|---|---|---|
| Личный бот, polling, SQLite | 1 vCPU, 1 ГБ RAM, 20 ГБ NVMe | Один процесс, сотни пользователей |
| Прод: webhook + nginx + PostgreSQL + Redis | 2 vCPU, 4 ГБ RAM, 40–60 ГБ NVMe | Честный минимум для боевого бота |
| Рассылки на 10k+ и фоновые задачи | 4 vCPU, 8 ГБ RAM, 80 ГБ NVMe | Воркеры Celery/ARQ, рассылка не тормозит хендлеры |
Свой telegram-bot-api под файлы >20 МБ | 4 vCPU, 8 ГБ RAM, 200 ГБ NVMe | Файлы лежат у вас, диск уходит быстро |
Честно про минимум: 1 ГБ работает ровно до момента, когда рядом поднимают PostgreSQL и Redis. Дальше OOM-killer выбирает процесс с самым большим RSS — а это ваш Python. Проверка: journalctl -k | grep -i "killed process"; есть строка — дело не в коде, а в железе. Подробный расчёт — в статье про нехватку RAM.
Локация — Лондон, и по неочевидной причине. Пинг между пользователем и вашим сервером не значит ничего: пользователь общается с Telegram, а не с вами. Важна только задержка «сервер → Bot API», и на рассылке она умножается на число получателей — каждая отправка отдельный HTTPS-запрос. С лондонской площадки curl -w 'connect %{time_connect} total %{time_total}' до api.telegram.org даёт порядка 0,008 и 0,03 секунды: Telegram отвечает из европейского дата-центра. Из российских сетей бывает в пять-десять раз хуже, плюс потери пакетов, и рассылка на три минуты растягивается на двадцать.
Когда иначе: бот ходит в OpenAI или Claude — берите США, там чистые IP и прямой доступ к ИИ-сервисам; бот собирает персональные данные россиян — площадку в РФ. Оплата — картами российских банков, по СБП, криптой или токеном MAAT: зарубежная карта не нужна.
Путь с нуля — в пошаговой настройке VPS под Telegram-ботов, выбор режима апдейтов — в разборе webhook или polling, площадки — в обзоре VPS в Великобритании для бота.
Развернуть за пару минут
Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Развернуть бот-стекОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Частые вопросы
В логе Conflict: terminated by other getUpdates request — что это?
Один токен опрашивают два процесса: старый не умер или запущена локальная копия с боевым токеном. Найдите его через ps -eo pid,etimes,cmd | grep "[b]ot.py" и запускайте один экземпляр под flock -n.
Webhook установлен без ошибок, но бот молчит.
Смотрите getWebhookInfo: pending_update_count и last_error_message назовут причину — обычно это неполная цепочка сертификата (fullchain.pem) или 502, потому что приложение за nginx не слушает порт.
Рассылка падает на середине с 403 Forbidden.
Часть подписчиков заблокировала бота — штатная ситуация. Ловите Forbidden отдельно, помечайте такие чаты неактивными и продолжайте цикл; RetryAfter отрабатывайте паузой ровно на retry_after секунд.
Нужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.