• Indonesian
  • English
  • Python Server Monitoring Bot: Step-by-Step Guide 2026

    Kecepatan:
    ⏱ 13 min read

    So here’s the deal: last week I was sitting with a coffee, mentally preparing for yet another “quick” script to watch a few servers, when I realized I’ve done this exact thing a dozen times. Check the endpoint, check the endpoint again, send a Telegram message if it’s dead. The logic is simple, the boilerplate is identical every single time, and the only thing that ever changes is the list of hosts. So this time I tried something a little different—I let OpenCode help me write the whole thing. And honestly? It was surprisingly chill.

    Before you roll your eyes at yet another “AI writes your code” article, hear me out. I’m not going to claim OpenCode replaces thinking, or that you can copy-paste your way to a flawless bot. What I found is that it handles the boring 80%—the boilerplate, the wiring, the “which function does what again?” moments—and leaves you to focus on the logic that actually matters for your environment. That’s a pretty fair trade if you ask me.

    Difficulty: Beginner
    Last Updated: August 2026
    Tested On: Ubuntu 22.04 LTS, Python 3.10, OpenCode 0.3.x, requests 2.31, python-telegram-bot 21.x

    Here’s the thing though: building a monitoring bot fully by hand is one of those tasks that’s simple in theory but quietly annoying in practice. You need the right library, clean token handling, a loop that doesn’t drift, and error handling that doesn’t either explode or silently eat exceptions. And if you’re new to python-telegram-bot, the async side of it can spin your head around. It’s honestly easy to burn an entire weekend and end up with something that works… only until the first network blip.

    Then there are the parts that bite you later, not now. Alert fatigue, for one—a bot that spams “server down” over and over will get muted by your team faster than you can say “on-call”. False positives from transient timeouts. No recovery detection, so you never find out when the box actually came back. These are exactly the small details that separate a hobby script from something you’d trust in production. And that’s the difference I want to show you here.

    Okay, so what I’ll walk you through is a setup I genuinely use: a Telegram bot that checks a few health endpoints every 60 seconds, fires an alert when something drops, and sends an all-clear when it’s back. We’ll build it with Python, use OpenCode to generate and iterate on the code, test it locally, and finally ship it as a systemd service so it survives reboots and crashes. Sound good? Grab a coffee—this shouldn’t hurt.

    architecture of a Python server monitoring bot built with OpenCode

    What We’re Building

    Think of it like a friendly watchman. Your bot sits on a server, every 60 seconds it pokes each of your health endpoints, and the moment one stops responding, it pings your Telegram with a clean “DOWN” message. When the endpoint comes back, it sends an “UP” message so you know the drama is over. Nothing fancy, no dashboards, no metrics collection—just a reliable heartbeat check that goes straight to your pocket.

    The nice part is the pattern scales. The same skeleton works for checking SSL expiry, disk space, or whether a backup file is fresh. Once you understand the loop and the state tracking, you can point the bot at almost anything you care about. That’s the real value here, not just the code itself.

    What You’ll Need

    Let’s keep the prerequisites honest, nothing exotic:

    • A Linux server or VPS—Ubuntu 22.04 works great, most distros are fine
    • Python 3.9 or newer (check with python3 --version)
    • OpenCode installed and connected to your AI provider of choice
    • A Telegram account and the BotFather to create your bot token
    • Access to the endpoints you want to monitor

    If you’re new to securing your box before putting scripts on it, my VPS hardening basics post is a quick read and worth doing first. You’d be surprised how many people deploy a monitoring bot onto a server that’s wide open.

    Step 1: Project Setup

    Let’s set up a clean workspace. I’m using a directory at /opt/monbot, but any path works. The key move here is the virtual environment—it keeps the project’s Python packages isolated from the system Python, which means you won’t accidentally break system tools when you upgrade a library.

    mkdir -p /opt/monbot
    cd /opt/monbot
    python3 -m venv venv
    source venv/bin/activate
    pip install --upgrade pip
    pip install requests python-telegram-bot

    Two libraries, two jobs. requests does the health checks and the Telegram API calls, python-telegram-bot is there if you later want richer Telegram features like keyboards or command handlers. For a simple poller, requests alone would honestly be enough—but having the second library installed doesn’t hurt and opens the door for upgrades later.

    Quick sanity check before moving on: run python3 -c "import requests" inside the venv. If it returns silently, you’re good. If you get a ModuleNotFoundError, it usually means the venv isn’t active. That’s the most common stumble, and the fix is always the same one-liner: source venv/bin/activate.

    Step 2: Grab Your Telegram Pieces

    Before OpenCode writes a single line, let’s collect the two things the bot needs: the token and the chat ID.

    Talk to @BotFather on Telegram, hit /newbot, pick a name, and it hands you a token. That token is basically the password for your bot—treat it like one. The chat ID is the unique number of your conversation with the bot. The easiest way to get it: message your bot once, then ask Telegram what happened.

    curl "https://api.telegram.org/bot<TOKEN>/getUpdates"

    In the JSON response, look for the chat block and grab its id. That number becomes CHAT_ID. If the response is empty, it means your bot hasn’t received any message yet—send one first, then retry. Takes about a minute total, and you only do it once.

    Step 3: Your First OpenCode Prompt

    Now for the fun part. Fire up OpenCode inside your project folder:

    cd /opt/monbot
    opencode

    Then describe what you want. The more specific you are, the better the first draft. Something like this works well:

    Build a simple Python monitoring bot. It checks several HTTP endpoints every 60 seconds. If an endpoint doesn't respond or returns anything other than 200, send a "DOWN" alert to Telegram. When a previously down endpoint recovers, send an "UP" alert. Use requests and python-telegram-bot. Put the token and chat id as variables near the top so they're easy to change.

    Watch it work. OpenCode reads your project structure, might ask a clarifying question or two, and then writes the file. Here’s where I’ll be a little preachy though: don’t trust it blindly. Read what it produced, understand the flow, then test it. The AI is the writer, but you’re the reviewer. That split is exactly where the quality comes from.

    And when the first draft isn’t quite right, just iterate. “Change the interval to 30 seconds”, “add a 5-second timeout per request”, “read the host list from a config file”. Each round is faster than writing from scratch, and you stay in control the whole time. For more on why this tooling matters in a server context, I covered the basics of systemd and cron automation separately.

    Step 4: Read the Code It Wrote

    Here’s roughly what your first draft should look like, give or take a few style choices. Notice the small things: a send_alert helper so you’re not repeating the API call, a timeout on every request, and—most importantly—a dictionary tracking the previous state of each host.

    import requests
    import time
    
    TOKEN = "YOUR_BOT_TOKEN"
    CHAT_ID = "YOUR_CHAT_ID"
    
    HOSTS = {
        "web-prod": "http://10.0.0.11/health",
        "api-prod": "http://10.0.0.12/health",
    }
    
    def notify(message):
        url = f"https://api.telegram.org/bot{TOKEN}/sendMessage"
        requests.post(url, json={"chat_id": CHAT_ID, "text": message}, timeout=10)
    
    states = {name: False for name in HOSTS}
    
    while True:
        for name, endpoint in HOSTS.items():
            try:
                up = requests.get(endpoint, timeout=5).status_code == 200
            except requests.RequestException:
                up = False
            if not up and not states[name]:
                states[name] = True
                notify("DOWN: " + name)
            elif up and states[name]:
                states[name] = False
                notify("RECOVERED: " + name)
        time.sleep(60)

    See what the state tracking buys you? The bot alerts once when a host drops, and once when it returns. It doesn’t nag you every 60 seconds while the server is down. That single dictionary is the difference between a useful tool and something your team mutes within an hour.

    The try/except is equally important. A dead endpoint doesn’t always return an HTTP error—sometimes it just times out and the library throws. If you don’t catch that, the whole script crashes on the first silent failure, which defeats the entire purpose. Detail like that is easy to miss when you’re typing fast, but a second pair of eyes on the code catches it.

    Step 5: Test It Before You Trust It

    Please don’t point this at production on the first run. Test locally, watch for three things: alerts arrive, they don’t duplicate, and nothing crashes.

    source venv/bin/activate
    python bot.py

    The cleanest test uses a dummy endpoint. Spin up a throwaway HTTP server on the same box:

    python3 -m http.server 8080

    Add it to your HOSTS with something like {"test-local": "http://127.0.0.1:8080/health"}, restart the bot, then kill the dummy server. You should get a “DOWN” alert. Start it again, and you should get a “RECOVERED” alert. Exactly one of each—that’s your signal that the state logic is sound.

    If you see duplicates, the state isn’t being persisted correctly across loop iterations. If you see nothing at all, the token or chat ID is probably wrong, so start there. If the script dies mid-run, the exception handling is missing something. Each failure mode points to a different line, and each is quick to fix.

    Step 6: Ship It as a systemd Service

    Running a bot from a terminal is fine for testing, terrible for real life. The moment you close the SSH session, it’s gone. The right move is to register it as a systemd service so it starts on boot, restarts on crash, and runs regardless of who’s logged in.

    Create the unit file:

    sudo nano /etc/systemd/system/monbot.service

    And fill it with this:

    [Unit]
    Description=Telegram Monitoring Bot
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    WorkingDirectory=/opt/monbot
    ExecStart=/opt/monbot/venv/bin/python bot.py
    Restart=always
    RestartSec=15
    
    [Install]
    WantedBy=multi-user.target

    Two details worth repeating: ExecStart points at the Python binary inside the venv, not the global one, and Restart=always with a sensible RestartSec means a crash recovers on its own without hammering the box with immediate restarts.

    Then enable and start it:

    sudo systemctl daemon-reload
    sudo systemctl enable monbot
    sudo systemctl start monbot
    sudo systemctl status monbot

    You want to see active (running). If it says failed, don’t panic—the logs will tell you exactly what’s wrong.

    sudo journalctl -u monbot -n 30 --no-pager

    In practice, the usual culprits are a wrong ExecStart path, the venv not existing where you expect it, or the script crashing on startup because a variable is empty. All of them show up clearly in journalctl. If you’re not used to digging through service logs, my guide on reading server logs efficiently will get you up to speed fast.

    Notes From the Trenches

    Now that it’s running, a few lessons I’ve collected the hard way:

    • Stop hardcoding the token. Move it to an environment variable or a config file with locked-down permissions. A token in a screenshot or a pasted log is a leaked token.
    • Don’t get cute with the interval. Every 10 seconds sounds impressive, but ten hosts means sixty requests a minute, and Telegram has rate limits. Sixty seconds covers the vast majority of monitoring needs without annoying anyone.
    • Keep the alert logic dumb and predictable. One DOWN on transition, one RECOVERED on transition. Resist the urge to add retry counters that spam people. Boring alerts are reliable alerts.
    • Always set a timeout. Without one, a host that silently drops packets will freeze your bot indefinitely. Five seconds is a good default.
    • Check the clock. If the server’s time is off, alert timestamps lie, and incident tracking gets messy fast. NTP isn’t optional.

    Troubleshooting at a Glance

    Save yourself some midnight scrolling with this quick reference table. It covers the issues I’ve actually seen people hit.

    Symptom Likely Cause Fix
    No alerts arrive at all Wrong token or chat ID Verify token via getMe, confirm chat ID from getUpdates
    Alerts repeat over and over State tracking broken Make sure the state dict updates both ways, check the logs
    ModuleNotFoundError on start Packages installed outside the venv Activate the venv, then reinstall
    No alert when a host truly dies Timeout too generous Drop the timeout to 5 seconds for faster detection
    Service keeps restarting Unhandled exception crashing the script Wrap the loop in try/except and check journalctl

    Wrapping Up

    And that’s really all there is to it. With a bit of Python and OpenCode helping with the grunt work, you can have a working monitoring bot in an afternoon instead of a week. The secret isn’t the AI doing everything for you—it’s the loop of generating, reviewing, testing, and iterating that gets you to a solid result fast.

    The same pattern carries over to other bots too: backup notifications, SSL expiry reminders, disk space warnings. Same loop, different endpoint, different message. Once you’ve internalized the state-tracking idea, you can point this at almost anything you care about. And if you want more monitoring ideas beyond bots, the Linux server monitoring tools post is a good companion read.

    Alright, that’s it from me. Hopefully this saves you a weekend of boilerplate. And hey—if you end up with a slightly fancier version, I’d genuinely be curious what you changed. Happy monitoring!

    Q: Do I need a public IP or domain for this bot?

    No. Since we’re using polling rather than webhooks, the bot only needs outbound access to api.telegram.org. The monitored servers don’t need public IPs either—they just have to be reachable from the machine running the bot.

    Q: Can I use webhooks instead of polling?

    Yes, but for an internal monitoring bot, polling is simpler and needs no public endpoint or reverse proxy. Webhooks make sense when your bot must answer user commands in real time and you already run a reverse proxy. For this use case, polling is the right call.

    Q: What should I do if my bot token leaks?

    Revoke it immediately via @BotFather. Revoking invalidates the old token on the spot, so even if it’s already out there, nobody can use it. Then swap it in your config and restart the service. Figure out how it leaked so it doesn’t happen again.

    Q: The bot never sends alerts—what do I check first?

    In order: confirm the token works via getMe, confirm the chat ID is correct via getUpdates, then run the script manually. If it works manually but not as a service, the problem is in systemd. The first lines of journalctl usually tell you exactly what’s wrong.

    Author: Syslog Solutions — NOC & Server Management Team. We handle 500+ servers daily, from shared hosting to enterprise dedicated infrastructure.