NEW 📢 Limited launch offer: up to 70% off your first month on every game!See plans

How to host a telebot bot 24/7

Telegram bot hostingUpdated Nov 6, 202612 min read

To host a telebot bot 24/7 you run it on a machine that stays on: a Linux VPS with a systemd service that starts it again when it stops, or a bot plan on PlayNHost (from US$1.99 a month), where you upload the finished program and the panel gives it the token and starts it again if it crashes. In our test a telebot 3.3.8 bot (gopkg.in/telebot.v3) used about 11 MB of memory. A Go bot is built on your own computer as one Linux x86-64 program: nothing is compiled on the server. The steps on PlayNHost:

  1. Build your bot for Linux x86-64 on your computer, as shown under Build it for Linux below.
  2. On Telegram bot hosting, click Create Server on a plan. In the order window, answer What is your bot for? with Telegram and pick Go.
  3. Open your server in the panel. The Set up your bot card on Overview opens at Connect to Telegram; its first step, What runs, should say This bot runs Go Bot.
  4. At Connect to Telegram, paste the token from @BotFather under Paste your bot token, click Connect, check the bot name shown and click Yes, continue.
  5. At Your code, click Upload files, then in Files click Upload and pick your program, named bot. Or let the server fetch it: on the Bot page, pane Code, card Settings, set Download link (optional) to an https link to the program and click Save.
  6. Click Start bot and open Console. After [PlayNHost] Starting your bot your bot prints Logged in as @your_bot, and the chip on Overview turns to Online in Telegram.
  7. On the Bot page, pane Telegram, click Open in Telegram and send your bot /start.

Creating the bot in @BotFather and the setup card are explained in How to host a Telegram bot. The Go Bot type itself (the Entry file, Start arguments (optional) and the download link) is the same for Discord and Telegram bots: How to host a Go Discord bot. This page is about telebot: what it uses on a server and what to know before you host it.

What we measured

LibraryVersionMemory at restStart to the platform's answerProgram size
telebot (Telegram)3.3.811 MB0.1 s8.1 MB
go-telegram/bot (Telegram)1.27.011 MB0.1 s8.1 MB
discordgo (Discord)0.29.012 MB0.3 s9.0 MB
A Go program with no libraryGo 1.23.122 MBnone2.1 MB

How: on our own node, in a throwaway container set up like a 1 GB bot plan with one CPU and the Debian image our Go Bot type runs, each library's program called Telegram or Discord with a fake token, which was refused; the program with no library made no call. Memory is the resident memory 15 seconds after that answer, start time runs from launch to the answer, and each figure is the median of runs 2 to 4. The programs were built with Go 1.23.12 and CGO_ENABLED=0.

These are floors: a program with no chats and no data yet. Your own code, what it keeps in memory and the number of chats add to them, so a minimal telebot program uses a small part of the 0.5 GB STARTER plan.

A minimal telebot bot

It answers /start, reads the token the panel gives it, prints a ready line, says plainly when Telegram refuses the token and stops cleanly when you press Stop. Put both files in one folder, then run go mod tidy, which downloads telebot and writes go.sum.

go.mod

module bot

go 1.23

require gopkg.in/telebot.v3 v3.3.8

main.go

package main

import (
	"errors"
	"fmt"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	tele "gopkg.in/telebot.v3"
	"gopkg.in/telebot.v3/middleware"
)

func main() {
	token := os.Getenv("TELEGRAM_BOT_TOKEN")
	if token == "" {
		fmt.Println("TELEGRAM_BOT_TOKEN is not set. Paste your token on the Bot page, Telegram, then restart.")
		os.Exit(1)
	}
	// Network errors contain the request address, and the address contains the token.
	hide := func(err error) string { return strings.ReplaceAll(err.Error(), token, "<token>") }

	b, err := tele.NewBot(tele.Settings{
		Token:  token,
		Poller: &tele.LongPoller{Timeout: 10 * time.Second},
	})
	if errors.Is(err, tele.ErrUnauthorized) {
		fmt.Println("Telegram did not accept the bot token. Paste the current one from @BotFather, then restart.")
		os.Exit(1)
	}
	if err != nil {
		fmt.Println("Could not start the bot:", hide(err))
		os.Exit(1)
	}

	// Without this, a panic in one handler stops the whole program.
	b.Use(middleware.Recover())

	b.Handle("/start", func(c tele.Context) error {
		return c.Send("Hello! I am running on PlayNHost.")
	})

	// A webhook left on the bot blocks polling. Remove it and keep the messages that are waiting.
	if err := b.RemoveWebhook(); err != nil {
		fmt.Println("Could not remove the webhook:", hide(err))
	}

	fmt.Println("Logged in as @" + b.Me.Username)
	go b.Start()

	// The panel stops the bot with Ctrl+C (SIGINT).
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
	<-stop
	b.Stop()
}

The line Logged in as @your_bot is what turns the chip to Online in Telegram. The panel gives your program the token as TELEGRAM_BOT_TOKEN, and also as TOKEN, BOT_TOKEN, TELEGRAM_TOKEN and TELOXIDE_TOKEN. telebot's own README example reads os.Getenv("TOKEN"), so its token line works unchanged too.

Build it for Linux

From your project folder, on any computer:

# macOS or Linux
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bot

# Windows PowerShell
$env:GOOS="linux"; $env:GOARCH="amd64"; $env:CGO_ENABLED="0"; go build -o bot

CGO_ENABLED=0 makes one self-contained file that does not depend on the server's system libraries, so it runs on our Debian image whatever system you build on. The result is a file called bot with no extension. A Windows .exe or a macOS build does not run on the server: the console then says [PlayNHost] bot is not a Linux program. Build it for Linux x86-64 (not Windows or macOS) and upload it again.

If the token is wrong

In our test, with a fake token, tele.NewBot returned this error (our test program printed it after the error's type):

*telebot.Error: telegram: Unauthorized (401)

NewBot asks Telegram who the bot is (getMe) before it returns, so a refused token stops the program before it polls. telebot returns its ErrUnauthorized error for Telegram's Unauthorized answer. The README example ends the program with log.Fatal(err); the bot above prints Telegram did not accept the bot token. Paste the current one from @BotFather, then restart. instead, a line the panel recognises: it shows the banner Telegram did not accept your bot token above Console.

To fix it, in @BotFather send /mybots, choose your bot, then API Token, and copy the token again. On the Bot page, pane Telegram, click Replace token, paste it, click Save token, then Restart now.

What to know about telebot on a server

  • Errors while polling are silent. When asking Telegram for messages fails, telebot's LongPoller tries again at once, with no pause, and prints the error only when Verbose is on. So when another copy of the bot polls with the same token (409 Conflict), a webhook is set, or the token is revoked while the bot runs, your bot keeps running but gets few or no messages, and the console stays quiet. Stop every other copy before you start it here, and see Fix Telegram bot 409 Conflict.
  • Seeing what polling does. Verbose: true in tele.Settings logs every request and Telegram's answer, so the 409 Conflict text appears in Console and the panel can show Your bot is running somewhere else too. It also logs the messages people send your bot, so turn it on for a test, not for good.
  • A webhook left on the bot. b.Start() does not remove a webhook, and while one is set polling fails silently. The bot above calls b.RemoveWebhook() before it starts, which keeps the messages that were waiting. You can also click Remove webhook on the Telegram pane. telebot's Webhook poller needs a public HTTPS address, which our plans do not include yet: use LongPoller.
  • A panic in a handler ends the program. telebot runs each handler in its own goroutine (unless Synchronous is on), and a panic there stops everything. b.Use(middleware.Recover()) turns a panic into an error that is printed instead. Call b.Use before b.Handle: middleware is attached when a handler is added.
  • Handler errors are printed for you. An error your handler returns goes to telebot's default error handler, which prints the update number and the error. Set OnError in tele.Settings to change that.
  • Your token in error messages. telebot puts the token in each request address (/bot followed by the token), and Go's network errors include that address. The bot above replaces the token before it prints an error. Do not share raw console output from a bot that prints errors as they are.
  • A clean stop. The panel stops a Go bot with Ctrl+C (SIGINT). b.Start() runs until b.Stop() is called, so the bot above starts it in a goroutine, waits for the signal and then calls b.Stop(), which cancels the request in progress.
  • Updates. The Go Bot type has no Packages field and no Git source: your modules are inside the program. To update, delete the old program in Files, upload the new build and restart. With a download link, publish the new build at that link, delete the program in Files and press Start.

Host it yourself on a Linux VPS

Copy the program you built to the VPS; it needs no Go there, and a build with CGO_ENABLED=0 does not depend on the VPS's system libraries. Make a user for the bot, put the program in its folder and keep the token in a file only root can read:

sudo useradd --system --create-home --home-dir /opt/mybot mybot
sudo install -o mybot -m 755 bot /opt/mybot/bot
sudo sh -c 'echo "TELEGRAM_BOT_TOKEN=123456789:your-token" > /etc/mybot.env'
sudo chmod 600 /etc/mybot.env

Create /etc/systemd/system/mybot.service:

[Unit]
Description=telebot Telegram bot
Wants=network-online.target
After=network-online.target

[Service]
User=mybot
WorkingDirectory=/opt/mybot
EnvironmentFile=/etc/mybot.env
ExecStart=/opt/mybot/bot
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Start it now and at every boot, then follow its output:

sudo systemctl daemon-reload
sudo systemctl enable --now mybot
sudo journalctl -u mybot -f

systemctl stop sends SIGTERM by default, which the bot above also listens for, so it stops cleanly. Restart=always starts the bot again after a crash or an exit. Stop any other copy first: with telebot a 409 Conflict shows nothing in journalctl either.

Self-hosting or a bot plan

On a VPS you manage yourself:

  • Operating system updates and security.
  • Copying each new build, the service file, the token file and its permissions.
  • Reading journalctl to find out why the bot stopped.
  • Backups of the bot's data.

In return a VPS runs anything, and you can give it a domain with HTTPS for a webhook.

On a PlayNHost bot plan the panel does this for you:

  • A Debian image that runs a Linux x86-64 program, and a check at start that says so when the file is a Windows or macOS build.
  • Upload in Files, or a download link the server fetches on start.
  • The token, checked with Telegram, stored encrypted and given to your program as TELEGRAM_BOT_TOKEN and TOKEN.
  • Starting the bot again after a crash. If it crashes again within a minute, it stays stopped and Console says why.
  • A Remove webhook button on the Telegram pane, and a banner above Console when your bot prints that Telegram refused the token.

Questions people ask

Should I use telebot v3 or v4?

v3.3.8 (6 Aug 2024) is the newest stable version, and the one we measured. Work has moved to v4: it is the repository's default branch and its README installs gopkg.in/telebot.v4, but it is still a beta (v4.0.0-beta.10, 16 Jun 2026). The v4 betas add newer Telegram Bot API versions, up to Bot API 10.1 in v4.0.0-beta.10, which v3.3.8 does not have. The README's getting-started example is the same in both apart from the import path. Both run the same way on our servers: build, upload, start.

Why does my telebot bot run but not answer?

Most often another copy is polling with the same token, on your PC or an old host, or a webhook is set. telebot does not print either problem, so the console looks normal. Stop the other copy, or revoke the token in @BotFather and paste the new one on the Telegram pane; check the same pane for a webhook. To see the cause, restart once with Verbose: true.

Can I use telebot's webhook instead of polling?

Not yet. A webhook needs a public HTTPS address, which our plans do not include yet. LongPoller needs no address: the bot asks Telegram for new messages, with a 10-second wait in the bot above.

telebot or go-telegram/bot?

Both run on the Go Bot type the same way. In our test go-telegram/bot used 11 MB against 11 MB for telebot, so memory does not decide between them. go-telegram/bot's newest release is v1.27.0, of 11 Sep 2026. If you follow a tutorial, use the library it uses.

Ready to run your telebot bot around the clock? See the plans on Telegram bot hosting.

Already hosting your bot somewhere else? Move it to us

OUR LATEST BLOGS