Бот работал, отвечал на команды, а потом начал сбоить: сообщения приходят через раз, часть команд теряется, а в логах по кругу одна и та же строка — 409 Conflict: terminated by other getUpdates request. Код здесь виноват меньше всего: Telegram отдаёт очередь новых сообщений только одному получателю, а на ваш токен претендуют две копии бота одновременно. Разбираем, откуда берётся лишняя копия, как её найти и как сделать так, чтобы конфликт не вернулся.
Что означает ошибка 409 Conflict
Один токен — один получатель обновлений. Метод getUpdates работает как очередь: Telegram отдаёт накопленные сообщения тому, кто первым их запросил. Как только вторая копия бота запрашивает обновления с тем же токеном, сервер переключает очередь на неё, а первую обрывает той самой строкой:
409 Conflict: terminated by other getUpdates request
make sure that only one bot instance is running
Дальше начинается перетягивание каната. Обиженная копия запрашивает обновления снова, выбивает вторую, та возвращается — и так по кругу. Из-за этого часть сообщений уходит копии, которую вы уже не считаете рабочей, и бот выглядит так, будто отвечает случайно. Это не сбой Telegram, не ошибка в вашем коде и не проблема хостинга: это признак двух запущенных экземпляров.
Откуда берётся вторая копия
Бот всё ещё крутится на вашем компьютере. Самая частая причина: вы запустили бота из терминала или из редактора, потом задеплоили на сервер, а локальный процесс так и остался жив. Закройте окно, из которого бот запускался, остановите процесс в редакторе и проверьте соседние проекты — запустить бота можно из любого из них. Отдельный случай — автозапуск: если бот когда-то добавлялся в автозагрузку, он возвращается после каждой перезагрузки, и о нём легко забыть.
Дубликат проекта на хостинге. Бывает, что проект просто опубликован дважды: старый не удалили, а рядом создали новый с тем же токеном от BotFather. Откройте список проектов и остановите лишний. Если в панели видны живые логи и статус, дубликат вычисляется мгновенно — в логах второй копии будет ровно та же строка про getUpdates.
Забытый процесс на старом сервере. Вторая копия часто живёт там, где бот когда-то хостился: на старом VPS, в аккаунте другого хостинга, на рабочем компьютере, на чужом ноутбуке, с которого вы показывали демо. Такой процесс не остановится сам и ничего не знает о переезде — он продолжает забирать сообщения.
Два проекта с одним токеном. Если у вас есть бот для себя и «тот же бот, но с правками» — при одном токене они будут конфликтовать всегда. Одному токену соответствует ровно один запущенный бот.
Как найти виновника
Начните с логов. Строка terminated by other getUpdates request в логах проекта — прямой указатель на то, что копия не одна. Посмотрите логи всех проектов, а не только текущего.
Проверьте процессы на сервере. На VPS или в контейнере это делается двумя командами:
ps aux | grep -i python
docker ps -a
Проверьте свою машину. Локальный терминал, вкладка редактора, фоновый процесс — всё, где вы когда-либо запускали бота. Список процессов пригодится и здесь:
ps aux | grep bot
Как починить: оставить одну копию
Остановите всё лишнее. Завершите процесс на своём компьютере (Ctrl+C в терминале или kill <PID>), удалите дубликат проекта на хостинге, погасите бота на старом сервере. Работающим должен остаться ровно один экземпляр.
Перезапустите оставшегося и дайте ему минуту. Предыдущий запрос к Telegram живёт до своего тайм-аута, поэтому ошибка может мигнуть ещё пару раз и пропадёт сама. Перезапуск — классический триггер конфликта: новая копия уже поднялась, а старая ещё не завершила работу. Если через несколько минут ошибка повторяется, значит, где-то осталась ненайденная копия — вернитесь к поиску.
Крайняя мера — отзыв токена. Если лишняя копия физически недоступна (например, на сервере, к которому у вас больше нет доступа), перевыпустите токен в BotFather: старый перестанет работать у всех копий. Новый токен нужно будет прописать в настройках проекта, иначе бот не стартует.
Если у бота настроен webhook
Тот же код 409 Conflict приходит и в другой ситуации: для бота установлен webhook, то есть Telegram сам присылает обновления на ваш адрес, а код при этом опрашивает getUpdates. Текст сообщения будет другим, код ошибки — тем же.
Проверьте webhook. Метод getWebhookInfo показывает, настроен ли адрес:
curl https://api.telegram.org/bot<ТОКЕН>/getWebhookInfo
Уберите webhook, если работаете через polling. Тут подойдёт deleteWebhook, а многие библиотеки умеют снимать webhook при старте одной опцией:
curl https://api.telegram.org/bot<ТОКЕН>/deleteWebhook
Выбирать нужно что-то одно: либо polling, либо webhook. Держать оба одновременно нельзя.
Отдельный токен для отладки
Правило на будущее: у продакшена и у тестов — разные боты. Создайте в BotFather второго бота с очевидным именем, например my bot test, и держите его токен отдельно от рабочего. Тогда локальные эксперименты перестанут ронять боевого бота, и создавать конфликт будет просто некому.
Где боту жить, чтобы конфликт не возвращался
Ошибка почти всегда лечится одинаково: обновления запрашивает ровно одна копия бота. Проще всего это даётся, когда бот живёт не на ноутбуке, который вы закрываете, а в одном постоянном месте. На Dockbox вы загружаете код, добавляете токен от BotFather в секреты, и бот работает сам — контейнер запущен постоянно, без засыпания и ограничений по времени работы, статус и живые логи видны в панели, а при сбое процесс поднимается автоматически. Лишняя копия или цикл перезапусков сразу заметны в логах.
Начать можно с тарифа Starter — 990 ₽/мес, причём первый месяц бесплатно (1 vCPU, 2 GB RAM). Если проектов и ресурсов нужно больше — есть Pro (2 490 ₽/мес) и Team (4 990 ₽/мес). Отдельных счетов за трафик и сборки нет, цены — в рублях.
Частые вопросы
Что означает ошибка 409 Conflict от Telegram?
Это код, который Telegram возвращает, когда обновления с одним токеном запрашивают сразу два клиента. Рядом стоит пояснение, что запрос прерван другим getUpdates, и просьба убедиться, что запущен только один экземпляр бота. Тот же код 409 приходит, если для бота установлен webhook, а код пытается получать обновления опросом.
Почему бот отвечает через одно сообщение?
Так выглядит конфликт двух копий: они по очереди забирают очередь обновлений, и часть сообщений уходит копии, которую вы уже не считаете рабочей. Со стороны это выглядит как случайные ответы и потерянные команды. Как только остаётся одна копия, поведение выравнивается.
Почему ошибка появилась сразу после деплоя на хостинг?
Потому что копия на вашем компьютере обычно всё ещё работает. Как только запускается копия на сервере, они сталкиваются, и логи заполняются строкой про getUpdates. Остановите локальный запуск — в течение минуты бот на хостинге начнёт отвечать нормально.
Можно ли запустить одного бота на двух серверах для надёжности?
Нет. При polling это не даёт никакой отказоустойчивости, а ломает бота: копии выбивают друг друга, и сообщения теряются. Один токен — одна работающая копия. Надёжность берётся другим: бота размещают там, где за проектом следят и поднимают его после сбоя.
Поможет ли отзыв токена в BotFather?
Отзыв токена — крайняя мера, но она работает, когда лишняя копия физически недоступна, например находится на сервере, к которому нет доступа. После перевыпуска старый токен перестаёт работать у всех копий сразу. Новый токен нужно вписать в настройки проекта, иначе бот не запустится.