← back to blog

Keeping a Telegram bot online through a server migration

Moving a Telegram bot to a new server sounds like it should be simple. Copy the code, copy the database, point things at the new box, done. In practice, most of the pain in a telegram bot migration doesn’t come from the code at all. It comes from how the Bot API actually delivers updates, and what happens when two servers both think they’re the one receiving them.

This is written from the operator’s side, not the developer’s. We host bots and manage the proxy layer around them, and almost every migration incident we’ve dealt with traces back to one of two things: a getUpdates conflict, or a webhook that got pointed at the new server before the new server was actually ready to answer.

Why bot migrations feel riskier than they are

A Telegram bot doesn’t have a “session” the way a user account does. There’s no device list, no login approval, no phone number tied to a physical SIM. The bot token is the only credential that matters, and it works from any IP address that can reach api.telegram.org. That’s the good news: you can run a bot from a laptop, then from a VPS in Singapore, then from a VPS in Frankfurt, and Telegram doesn’t care where the requests come from.

The risk isn’t Telegram rejecting your new server. It’s your own bot ending up in a state where two processes are both trying to be the “real” instance at the same time, or where updates arrive during the switchover window and nothing is listening.

Webhook bots versus polling bots

How your bot receives updates changes what a migration actually involves.

A polling bot calls getUpdates on a loop, telling Telegram “give me anything new since offset X.” Telegram holds the connection open for a while and responds when there’s something to send, or when the timeout expires. Nothing needs to be publicly reachable. The bot reaches out to Telegram, not the other way around.

A webhook bot registers a URL with setWebhook, and Telegram posts updates to that URL as they happen. This requires a public HTTPS endpoint with a valid certificate. Migration here means the endpoint itself has to change, or the thing behind the endpoint has to change while the URL stays the same.

These two setups fail differently during a migration, so the plan has to match which one you’re running.

The getUpdates conflict problem

If your bot polls, Telegram enforces something worth knowing before you migrate: only one getUpdates connection per bot token is allowed at a time. If a second process opens a polling connection while the first one is still active, Telegram returns a 409 Conflict to one of them. This isn’t a bug, it’s how Telegram prevents the same update from being processed twice by two different processes.

The failure mode during a migration is predictable. You bring up the bot on the new server while the old one is still running “just in case.” Both start polling. One of them starts throwing 409s, and depending on your error handling, that can mean silent failure, a crash loop, or duplicate replies if the old process keeps retrying and occasionally wins the race.

The fix is procedural, not technical: stop the old polling process completely before starting the new one. There’s an unavoidable gap here, usually a few seconds, where nothing is polling. Telegram queues updates for a bot that isn’t currently connected, so a short gap doesn’t lose messages, it just delays them until the new process starts pulling. What you’re avoiding is not gap time, it’s overlap time.

Moving a webhook without dropping updates

Webhook migrations have a different shape. Telegram pushes updates to whatever URL is currently registered, so the question is whether that URL stays constant or moves.

If you keep the same domain and only change what server sits behind it, the migration is mostly a DNS and reverse proxy exercise. Lower the DNS TTL ahead of time if you’re changing the IP the domain points to, bring the new server up and confirm it can serve the webhook path and respond with a 200, then cut over. Keep the old server running and reachable until you’ve confirmed the new one is actually receiving traffic, since a webhook that returns errors or times out will cause Telegram to back off and retry with delays, and repeated failures can get the webhook temporarily disabled.

If the domain itself is changing, the safer order is: bring the new server online first, run setWebhook to point at the new URL, and only then decommission the old one. Don’t drop the old server and then set the new webhook afterward, because that creates a window where the URL is either invalid or unreachable and updates queue up or, past Telegram’s retry limit, get dropped.

Either way, checking getWebhookInfo before and after the switch tells you what Telegram thinks is currently registered and whether there’s a backlog of pending updates or a recent delivery error. It’s worth checking this rather than assuming the setWebhook call succeeded just because the API returned ok: true.

What actually breaks during migration

Beyond the polling and webhook mechanics, a few things reliably trip people up:

The bot’s local state doesn’t move itself. Conversation history, rate limit counters, user preferences, whatever your bot keeps in a database or on disk, all of that has to be copied over and verified before the new server takes live traffic. Testing the bot’s logic on the new server with a stale or empty database will look like the migration worked when it actually didn’t.

Certificates matter more than people expect for webhooks. If you’re using a self-signed certificate registered directly with setWebhook rather than a CA-signed one behind a standard reverse proxy, the certificate has to be reuploaded when the endpoint changes, since Telegram pins to the certificate you gave it at registration time.

Outbound IP reputation can affect message delivery indirectly. Telegram’s own infrastructure doesn’t block a bot for changing server IPs, but if your bot sends links or triggers spam heuristics and the new server’s IP range has a worse reputation than the old one, you may see more aggressive rate limiting on message delivery to some clients. This is a network-layer concern, not a Bot API concern, and it’s easy to mistake for a migration bug when it’s actually the new host’s IP standing.

Proxy and network settings that don’t survive a copy paste

If your bot connects to Telegram through a proxy, whether that’s for reaching api.telegram.org from a restricted network or for routing outbound requests the bot makes to other services, the proxy configuration is almost never something you can copy verbatim between servers. A SOCKS5 or MTProto proxy entry tied to the old server’s IP as the source address, or a firewall rule that only allow-lists the old server’s egress IP, will silently break connectivity on the new box. The bot process starts, looks healthy, and then every outbound call times out.

Check this before cutover, not after. Confirm the new server can reach api.telegram.org directly if that’s how the bot is meant to work, or confirm the proxy is configured to accept connections from the new server’s IP if it’s meant to route through one. This is the single most common cause of a bot that “migrated fine” and then goes quiet an hour later once the old server, and whatever exception was quietly routing around the proxy issue, gets shut down.

A migration checklist that keeps the bot answering

  • Confirm what delivery method you’re on, webhook or polling, since the safe sequence differs
  • For polling, stop the old process fully before starting the new one and accept a short gap rather than any overlap
  • For webhooks, bring the new server up first, verify it answers correctly, then repoint or reissue setWebhook
  • Copy and verify the bot’s database or state store before cutover, don’t assume an empty state is fine to test against
  • Check getWebhookInfo after the switch to confirm Telegram sees what you expect
  • Verify proxy rules and firewall allow-lists reference the new server’s IP, not the old one
  • Keep the old server reachable but idle for a short window in case you need to roll back

What to do if something goes wrong anyway

If updates stop arriving after a webhook migration, getWebhookInfo will usually show a last_error_message explaining why Telegram couldn’t deliver, whether that’s a timeout, a certificate mismatch, or a non-200 response. If a polling bot starts throwing 409s, it means something old is still connected somewhere, often a forgotten test instance or a systemd service that didn’t actually stop. Kill every process holding the token, wait a few seconds, then start the one you want running.

If you’d rather have someone who deals with this daily handle the hosting and proxy side of the migration, that’s what we do at telegramvault.org.

Get new guides and videos first — join the Telegram channel.

need infra for this today?