MAATRIX / Блог / Перенос телеграм-бота без потери пользователей

Перенос телеграм-бота без потери пользователей

MAATRIX

Переезд телеграм-бота на другой сервер выглядит проще переезда сайта — вроде бы достаточно скопировать код и запустить его на новой машине. На деле есть момент, которого нет у обычного веб-проекта: пока бот "переезжает", пользователи продолжают писать ему сообщения, и от того, как вы организуете переключение, зависит — обработаются эти сообщения с небольшой задержкой или потеряются вместе с текущим шагом диалога. Разберём перенос по шагам с учётом того, как реально устроен Telegram Bot API.

Polling или webhook — от этого зависит вся стратегия переноса

Прежде чем планировать перенос, нужно понимать, в каком режиме работает ваш бот — это определяет весь дальнейший план.

Polling (getUpdates) — бот сам, в цикле, опрашивает серверы Telegram: "есть новые сообщения?". Инициатор соединения — ваш процесс бота. У этого режима есть важное для миграции свойство: Telegram API не позволяет двум процессам одновременно опрашивать обновления по одному и тому же токену. Если запустить бота и на старом, и на новом сервере одновременно, оба начнут получать ошибку 409 Conflict: terminated by other getUpdates request — работать будет то один, то другой, рывками, и часть сообщений может обрабатываться дважды или с большой задержкой. Из этого прямо следует требование к переносу: на старом сервере бот должен быть точно остановлен до старта на новом, и никогда — одновременно на обоих.

Webhook — вы регистрируете у Telegram HTTPS-адрес, куда он сам присылает обновления POST-запросом при каждом новом событии. Здесь конфликта двух инстансов физически нет — сообщения летят туда, куда указывает зарегистрированный URL, а не туда, где физически запущен процесс. Значит для переноса webhook-бота ключевое действие другое: не остановка старого процесса, а явное переключение зарегистрированного адреса на новый сервер методом setWebhook. Если этого не сделать, новый сервер может быть полностью готов и запущен, но Telegram продолжит слать обновления по старому адресу — и новый бот попросту не увидит ни одного сообщения.

Проверить свой текущий режим можно одной командой:

curl -s "https://api.telegram.org/bot<TOKEN>/getWebhookInfo"

Если в ответе "url": "" — бот работает через polling. Если там указан адрес — это webhook, и именно его придётся менять при переезде.

Дальше план разделяется на два сценария — они пересекаются в части базы данных и тестирования, но различаются в моменте самого переключения.

Сценарий 1: перенос polling-бота

Порядок действий, минимизирующий окно недоступности:

  1. Подготовьте новый сервер заранее — окружение, зависимости, код бота, но пока без реального токена в конфиге (или с процессом, выключенным через systemd/supervisor).
  2. Перенесите и синхронизируйте базу данными состояний диалогов (подробнее — в следующем разделе), не запуская на новом сервере процесс с боевым токеном.
  3. Убедитесь, что автозапуск бота на старом сервере отключён — иначе после ручной остановки его поднимет systemd или supervisor через несколько секунд.
  4. Остановите бота на старом сервере:
   sudo systemctl stop mybot
   sudo systemctl disable mybot
  1. Досинхронизируйте базу — если между шагом 2 и остановкой прошло время, накатите дельту (новые записи, если БД пишется в процессе работы бота).
  2. Запустите бота на новом сервере:
   sudo systemctl enable --now mybot
  1. Проверьте логи и первые входящие сообщения — убедитесь, что бот отвечает и не сыплет ошибками подключения к БД или внешним API.

Окно недоступности здесь равно времени между шагами 4 и 6 — в норме это секунды или первые десятки секунд, если процедура заранее отрепетирована на тестовом боте (см. ниже). Специально отключать бота "заранее и надолго" не нужно — весь смысл в том, чтобы этот интервал был как можно короче.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Сценарий 2: перенос webhook-бота

Здесь порядок действий чуть другой, потому что критичный момент — не остановка процесса, а регистрация нового адреса:

  1. Разверните бота на новом сервере, поднимите HTTPS (сертификат, домен или поддомен, реверс-прокси) — но пока не переключайте вебхук, новый адрес должен полностью отвечать сам по себе.
  2. Проверьте новый адрес вручную — либо curl-запросом с телом реального формата апдейта, либо через тестового бота (см. раздел про тестирование).
  3. Перенесите и синхронизируйте базу состояний.
  4. Переключите вебхук одной командой:
   curl -s "https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://new-server.example.com/webhook/<secret_path>"
  1. Сразу проверьте getWebhookInfo — убедитесь, что url действительно указывает на новый сервер и pending_update_count не растёт бесконтрольно (это будет означать, что новый сервер не отвечает 200 OK):
   curl -s "https://api.telegram.org/bot<TOKEN>/getWebhookInfo"
  1. Только после подтверждения, что новый сервер стабильно принимает и обрабатывает апдейты, отключайте старый сервер (или оставляйте его выключенным на всякий случай ещё сутки — это не критично, потому что Telegram уже не шлёт ему ничего).

У webhook-сценария окно недоступности потенциально даже короче, чем у polling: если новый сервер уже развёрнут и протестирован, сама команда setWebhook переключает трафик почти мгновенно — Telegram начинает слать следующие обновления сразу на новый адрес.

Почему короткое окно переключения — не катастрофа

Здесь важно понимать встроенное свойство Telegram Bot API, которое снижает риск полной потери сообщений при аккуратном и коротком окне переключения: Telegram не отбрасывает обновления мгновенно, если бот на секунды или минуты не отвечает.

  • Для polling: если бот не вызывает getUpdates, сервер Telegram копит очередь обновлений и отдаст их все разом, как только опрос возобновится (с учётом установленного offset).
  • Для webhook: если сервер по указанному адресу не отвечает 200 OK (недоступен, в процессе рестарта, отдаёт 5xx), Telegram делает повторные попытки доставки в течение ограниченного времени, прежде чем обновление будет считаться недоставленным.

Это не безлимитная гарантия — очередь хранится не вечно, и здесь принципиально важно не полагаться на точную цифру: конкретное время жизни очереди и число повторных попыток — это внутренняя логика Telegram, которая может отличаться от того, что вы читали в чужих статьях, и точный ориентир лучше не считать константой при планировании. Практический вывод один: окно в несколько секунд или даже пару минут — это некритичный риск, а окно в час — уже нет. Отсюда и требование готовить перенос заранее, репетировать его на тестовом боте и не растягивать паузу между остановкой старого и запуском нового процесса.

Отдельно: это свойство защищает именно *новые* входящие сообщения от пользователей в момент простоя. Оно не решает вопрос состояния диалога — если пользователь был на середине сценария (например, заполнял форму заказа шаг за шагом), доставленное с опозданием сообщение попадёт в бота, который должен понимать, на каком шаге находится этот пользователь. А это уже вопрос переноса базы, а не самого API.

Перенос состояния диалогов — самая частая причина "порванных" сценариев

Если бот простой и просто отвечает на команды без сохранения контекста — эта часть вам не грозит. Но большинство ботов с многошаговыми сценариями (оформление заявки, регистрация, FSM-состояния в aiogram или python-telegram-bot) хранят состояние где-то: в Redis, в PostgreSQL/SQLite, иногда прямо в памяти процесса (это отдельный антипаттерн — при рестарте состояние в памяти теряется независимо от переноса сервера).

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

Практическая последовательность для БД (на примере PostgreSQL):

# 1. Первичный дамп заранее, пока старый бот ещё работает — самый долгий шаг, время не критично
pg_dump -h old-server -U botuser -d botdb -F c -f botdb_initial.dump

# 2. Перенос дампа и восстановление на новом сервере (можно заранее, в фоне)
pg_restore -h new-server -U botuser -d botdb --clean --if-exists botdb_initial.dump

# 3. В момент самого переключения — короткий дифф изменений с момента шага 1
pg_dump -h old-server -U botuser -d botdb -F c -f botdb_delta.dump
pg_restore -h new-server -U botuser -d botdb --clean --if-exists botdb_delta.dump

Если БД небольшая (десятки–сотни мегабайт состояний), шаг 3 занимает секунды, и его можно безопасно уложить в то же окно, что и остановку/запуск процесса бота. Если состояний много и дамп занимает минуты — рассмотрите логическую репликацию (pg_logicaldecoding/pglogical) или хотя бы промежуточный rsync файла SQLite перед самым переключением, чтобы финальная дельта была маленькой. Про перенос базы отдельно от приложения — в статье про миграцию базы данных между серверами.

Для Redis, если состояния хранятся там как временные ключи с TTL (частый случай FSM-хранилища в aiogram), можно использовать redis-cli --rdb для снятия снапшота и загрузки его на новый сервер, либо, если позволяет архитектура — временно завести Redis на новом сервере как replica старого (REPLICAOF host port), дождаться полной синхронизации и только потом промоутить его в master и переключать на него бота. Это позволяет свести дельту к секундам без остановки Redis на старом сервере до последнего момента.

Тестирование на отдельном боте — не трогайте боевой токен до последнего

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

Правильный порядок:

  1. Создайте у @BotFather отдельного тестового бота (/newbot) — это буквально второй, независимый токен, который никак не связан с боевым.
  2. Разверните код на новом сервере с этим тестовым токеном в конфиге, укажите отдельный webhook-путь или запустите его в polling — не важно, какой режим у боевого бота, тестовый можно гонять как удобнее.
  3. Прогоните полный сценарий вручную: команды, многошаговые диалоги, запись в БД, интеграции с внешними API (платежи в тестовом режиме, если есть) — здесь же проверяется, что новый сервер вообще может достучаться до нужных портов и внешних сервисов (файрвол, DNS, исходящие соединения).
  4. Только когда тестовый бот стабильно отрабатывает на новом сервере несколько часов или дней (в зависимости от того, насколько бот критичен) — переходите к переключению боевого токена по одному из сценариев выше.
  5. После переноса можно оставить тестового бота на новом сервере как постоянный canary-инстанс для проверки будущих обновлений кода — это удобно и почти ничего не стоит.

Отдельный плюс такого подхода: тестовый бот параллельно проверяет весь остальной чек-лист сервера — установлен ли нужный рантайм (Python/Node), доступны ли нужные библиотеки, не блокирует ли файрвол исходящие запросы к api.telegram.org, корректно ли настроен реверс-прокси и TLS-сертификат для webhook. Всё это лучше найти на тестовом токене, чем на боевом.

Чек-лист непосредственно перед переключением

Короткий список, который стоит пройти прямо перед выполнением команды setWebhook или запуском бота на новом сервере:

ПроверкаКак проверить
Новый сервер отвечает на внешние запросыcurl тестового webhook-пути или ручной getUpdates с тестовым токеном
БД на новом сервере доступна и содержит актуальные данныеСверить количество записей / контрольную выборку со старым сервером
Автозапуск на старом сервере отключёнsystemctl is-enabled mybot должен вернуть disabled
Логирование на новом сервере включеноПервые же сообщения должны появляться в логах, а не тонуть молча
Секреты и переменные окружения перенесеныТокен, ключи внешних API, строка подключения к БД — сверить построчно
DNS/прокси уже смотрит туда, куда нужно (для webhook)dig/curl -v на домен бота, проверить актуальный IP
Откат подготовленСтарый сервер выключен, но не удалён — можно быстро вернуть как было

Последний пункт особенно важен: не удаляйте старый сервер и не стирайте с него код и базу сразу после переезда. Подержите его в выключенном, но доступном состоянии хотя бы несколько дней — если после переключения всплывёт проблема, откат (обратный setWebhook или обратный запуск процесса при отключённом новом) должен занимать минуты, а не требовать восстановления из архивных бэкапов. Общий принцип поэтапного переезда с проверкой на новом месте перед полным отключением старого разобран в статье про перенос сайта на новый VPS без простоя — механика та же, просто для бота роль "домена" играет вебхук или сам процесс.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать VPS

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

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

Можно ли просто скопировать код и запустить бота сразу на двух серверах для подстраховки?

Для polling — нет, это гарантированно даст 409 Conflict, и оба инстанса будут работать нестабильно. Для webhook — теоретически можно держать оба процесса запущенными, но реальные апдейты будут идти только туда, куда указывает текущий setWebhook, так что смысла в одновременной работе обоих нет — только в готовности второго к быстрому переключению.

Что будет, если во время переключения пользователь напишет боту сообщение?

Скорее всего, сообщение не потеряется мгновенно: Telegram либо отдаст его при следующем getUpdates (polling), либо повторит попытку доставки на webhook в течение ограниченного времени. Но если окно недоступности растянется на десятки минут или часы, полагаться на это не стоит — точный срок хранения очереди на стороне Telegram не документирован как жёсткая гарантия.

Нужно ли менять токен бота при переезде на новый сервер?

Нет, токен привязан к самому боту в BotFather, а не к серверу. Меняются только: где физически бегает процесс (для polling) или куда указывает setWebhook (для webhook). Токен остаётся тем же, если вы не хотите его сознательно перевыпустить (/revoke в BotFather) по соображениям безопасности.

Как убедиться, что вебхук реально переключился, а не просто команда выполнилась без ошибки?

Ответ setWebhook с "ok": true подтверждает только, что Telegram принял запрос. Дальше обязательно проверьте getWebhookInfo — поле url должно указывать на новый адрес, а last_error_message и pending_update_count покажут, действительно ли новый сервер отвечает корректно.

Что делать, если после переключения бот отвечает, но "не помнит" контекст диалога у части пользователей?

Это почти всегда означает, что дельта состояний между первым дампом БД и моментом переключения не была перенесена (см. раздел про перенос состояния) — либо синхронизация была сделана заранее, а переключение процесса случилось значительно позже.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →