DEV Community

Cover image for Your Telegram bot isn't crashing randomly. It's on a timer.
Boris Kl
Boris Kl

Posted on

Your Telegram bot isn't crashing randomly. It's on a timer.

The ticket always reads the same way: "the bot keeps crashing randomly, please fix."

So you pull the logs. And it's not ra
ndom at all. The bot dies on a schedule — every few hours, almost to the minute. Like clockwork.

That rhythm is a gift. Random failures are the hard ones. They smell like memory leaks, race conditions, bad hardware. A failure that lands on a schedule means something expires. A socket. A token. A connection some box in the middle decided was idle. You're not hunting a ghost. You're looking for a timer.

Here are the three timers I find over and over in Telegram bots that "crash randomly." Between them they cover most of the rescues I get called in for.

Timer #1: the connection that quietly went stale

A long-polling bot holds one HTTP connection open to Telegram and waits. The code looks alive. The process is running. But between your VPS and Telegram sits a NAT table or a proxy. It has an idle timeout. After a fixed window, it drops the connection and tells no one.

No error on your side. No error on Telegram's side. The socket is just... gone. Your bot keeps waiting on a connection that no longer exists. Depending on the library and its settings, it throws two hours later. Or it hangs forever.

The fix isn't a bigger server and it isn't switching libraries. It's assuming the connection will die and reconnecting like you mean it:

import time, logging, requests

def poll_forever(token):
    offset = 0
    backoff = 1
    while True:
        try:
            r = requests.get(
                f"https://api.telegram.org/bot{token}/getUpdates",
                params={"offset": offset, "timeout": 50},
                timeout=(5, 60),   # connect, read — never infinite
            )
            r.raise_for_status()
            backoff = 1
            for upd in r.json()["result"]:
                offset = upd["update_id"] + 1
                handle(upd)
        except requests.RequestException as e:
            logging.warning("poll failed: %s — reconnecting in %ss", e, backoff)
            time.sleep(backoff)
            backoff = min(backoff * 2, 60)
Enter fullscreen mode Exit fullscreen mode

Three things matter here. A real read timeout, set a bit above the long-poll window. Backoff that grows, so a Telegram hiccup doesn't turn into a hammering loop. And a log line every single time it happens. That last one is the sleeper. If reconnects are silent, you'll never know if they fire once a week or once a minute.

Frameworks like aiogram and python-telegram-bot handle much of this for you. But I keep meeting bots where someone turned retries off, or wrapped the library in their own loop that swallows the one exception that mattered.

Timer #2: two transports fighting over one bot

Telegram gives you two ways to receive updates: getUpdates polling or a webhook. One bot token gets exactly one. If a webhook is set and something calls getUpdates, Telegram answers with 409 Conflict, and plenty of homegrown loops treat that as a fatal crash.

How do you end up here without noticing? Easier than you'd think. Someone tested a webhook months ago and never deleted it. A deploy script starts a second copy before the first one dies. Or systemd restarts the service, the old process lingers for a minute still holding the connection, and the new one comes up screaming.

The symptoms look mystical: duplicate messages, updates arriving twice, the bot answering some users and ghosting others, crashes that only happen after a deploy. The cause is boring: two consumers, one queue.

The check takes ten seconds:

curl "https://api.telegram.org/bot$TOKEN/getWebhookInfo"
Enter fullscreen mode Exit fullscreen mode

If you're polling and that response shows a URL — there's your bug. Clear it with deleteWebhook and pick one transport for good. Then make sure only one copy of the bot can run. On systemd that's the default, unless your unit file fights it. The ghost-process case usually comes down to KillMode and TimeoutStopSec that don't match how the bot really shuts down. I've watched a service restart-loop for hours because a dead process refused to let go of port and token.

Timer #3: the request with no timeout

This one doesn't kill the process. It's worse — the bot stays green in every dashboard while doing absolutely nothing.

Somewhere in a handler there's a call to an outside API. Weather, payments, a CRM, your own backend. It's written as requests.get(url), no timeout, because it worked fine in testing. Then one day that API stops answering but keeps the TCP connection open. The default timeout in requests is none. So the handler blocks forever. And in a single-threaded polling loop, that means the whole bot is now a very quiet piece of furniture.

Users say "the bot stopped replying." Monitoring says everything's fine. The process has been "up" for nine days.

Every outbound call gets a timeout. No exceptions, including the calls to Telegram itself:

requests.get(url, timeout=(5, 30))  # 5s to connect, 30s to read
Enter fullscreen mode Exit fullscreen mode

And if the bot does real work, handlers shouldn't share one thread with the poller at all. Even a small thread pool changes the math. One dead upstream API costs you one worker, not the whole bot.

Make the next failure introduce itself

The pattern behind all three: the bot had no way to tell you what was going on. So the fix that outlasts any single bug is a heartbeat. One log line per minute: poll count, last-update time. Plus an alert when the process restarts more than a couple of times an hour. It's cheap to add. And it turns the next "it crashes randomly" into a two-minute read, because now the logs show the rhythm.

Uptime for a bot isn't a feature you bolt on at the end. It's a mindset. Assume every connection dies. Assume every API hangs. Assume every deploy leaves a ghost behind. Then write the ten extra lines that expect all of it.

I build and rescue Telegram bots for clients, and this trio is where I look first, before reading a line of business logic.

What's the strangest clockwork failure you've traced back to a timeout? And do you run bots on webhooks or polling — and why? Genuinely curious what breaks for other people.

Top comments (0)