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

How to host a grammY bot 24/7

Telegram bot hostingUpdated Nov 6, 202610 min read

To host a grammY 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 the panel installs your packages, gives your code the token and starts the bot again if it crashes. In our test a grammY 1.46.0 bot used about 60 MB of memory on Node.js 22. grammY's bot.start() uses long polling, which is what our plans need: they have no public HTTPS address for a webhook yet. The steps on PlayNHost:

  1. On Telegram bot hosting, click Create Server on a plan. In the order window, answer What is your bot for? with Telegram and pick Node.js.
  2. 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 Node.js Bot.
  3. 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.
  4. At Your code, click Upload files and upload your package.json and index.js without node_modules, or click Connect Git. No code yet? Create a starter bot writes a small grammY bot for you.
  5. On the Bot page, pane Code, check that Entry file is the file that starts your bot (index.js by default). Click Save if you change it.
  6. Click Start bot and open Console. npm installs grammY on the first start, then 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 panel steps are explained in How to host a Telegram bot. This page is about grammY itself: what it uses on a server and what to know before you host it.

What we measured

LibraryVersionMemory at restStart to Telegram's answerInstall size
grammY1.46.060 MB0.2 s3.0 MB
Telegraf4.16.360 MB0.2 s2.7 MB
node-telegram-bot-api2.3.063 MB0.2 s2.4 MB
Node.js 22 alone, no library22.23.443 MBnonenone

How: on our own node, in a throwaway container set up like a 1 GB bot plan (the Node.js 22 image and start command of our Node.js bot type, with Node's memory limit at 80 percent), each bot called Telegram with a fake token, which Telegram refused; 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.

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

A minimal grammY bot

It answers /start, reads the token the panel gives it and prints a ready line. Put both files in your bot's main folder.

package.json

{
  "name": "my-grammy-bot",
  "private": true,
  "main": "index.js",
  "dependencies": {
    "grammy": "^1.46.0"
  }
}

index.js

const { Bot, GrammyError } = require("grammy");

const bot = new Bot(process.env.TELEGRAM_BOT_TOKEN);

bot.command("start", (ctx) => ctx.reply("Hello! I am running on PlayNHost."));

// Without this, an error in one command stops the whole bot.
bot.catch((err) => {
  console.error(`Error while handling update ${err.ctx.update.update_id}:`, err.error);
});

// bot.start() only returns when the bot stops, so the ready line goes in onStart.
bot.start({
  onStart: (me) => console.log(`Logged in as @${me.username}`),
}).catch((err) => {
  if (err instanceof GrammyError && err.error_code === 401) {
    console.log("Telegram did not accept the bot token. Paste the current one from @BotFather, then restart.");
  } else {
    console.error(err);
  }
  process.exit(1);
});

The panel gives your code the token as TELEGRAM_BOT_TOKEN, and also as BOT_TOKEN, TOKEN, TELEGRAM_TOKEN and TELOXIDE_TOKEN, so process.env.BOT_TOKEN from other examples works too. The line Logged in as @your_bot is what turns the chip to Online in Telegram.

If the token is wrong

In our test, with a fake token, grammY's bot.start() stopped with this error:

GrammyError: Call to 'deleteWebhook' failed! (401: Unauthorized)

bot.start() sends getMe and deleteWebhook at the same time, so the error names whichever call Telegram refused first: in some of our runs it was Call to 'getMe' failed! (401: Unauthorized). grammY does not retry a refused token: bot.start() fails at once. The bot above catches that case and prints Telegram did not accept the bot token. Paste the current one from @BotFather, then restart. instead, and the panel 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 grammY on a server

  • The ready line goes in onStart. bot.start() returns only when the bot stops, so a console.log after await bot.start() never prints while it runs. grammY calls onStart with your bot's details just before it asks Telegram for the first messages.
  • An error in a command stops the bot unless you set bot.catch. Without it, grammY prints No error handler was set!, stops polling and throws the error, so the process ends. Our panel starts a crashed bot again within about a minute, but it is offline until then. With bot.catch, as above, the error is printed and the bot carries on.
  • A webhook left on the bot is removed for you. bot.start() calls deleteWebhook before it polls, and keeps the messages waiting unless you pass drop_pending_updates: true. This matters when you move a bot from a host that used a webhook: How to move a Telegram bot to PlayNHost.
  • Another copy of the bot stops this one. On 409 Conflict (the same token polling somewhere else) bot.start() fails instead of retrying. The panel shows Your bot is running somewhere else too: stop the copy on your PC or your old host. See Fix Telegram bot 409 Conflict.
  • Network errors at start are retried quietly. If the first call to Telegram fails on the network, grammY tries again with longer and longer waits (up to 20 minutes between tries in version 1.46.0) and prints nothing. To see those tries, on the Bot page, pane Environment, add the variable DEBUG with the value grammy* (Add variable) and restart.
  • Node.js version: grammY 1.46.0 asks for Node.js 12.20 or 14.13.1 and newer. The Runtime on the Code pane offers Node.js 20, 22 (recommended), 24, 25 and 26.
  • Memory limit: the start script sets NODE_OPTIONS=--max-old-space-size to 80 percent of your plan's memory, so the rest of the process has room. Details of the Node.js start script: How to host a Node.js Discord bot.

Host it yourself on a Linux VPS

On Debian 12 or 13, or Ubuntu 24.04, the distribution's package is enough for grammY (Debian 12 ships Node.js 18.20, Debian 13 ships 20.19, Ubuntu 24.04 ships 18.19):

sudo apt update
sudo apt install nodejs npm
node --version

Make a user for the bot, copy your files and install the packages:

sudo useradd --system --create-home --home-dir /opt/mybot mybot
sudo cp package.json index.js /opt/mybot/
sudo chown -R mybot: /opt/mybot
cd /opt/mybot && sudo -u mybot npm install --omit=dev

Keep the token out of your code, in a file only root can read:

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=grammY Telegram bot
Wants=network-online.target
After=network-online.target

[Service]
User=mybot
WorkingDirectory=/opt/mybot
EnvironmentFile=/etc/mybot.env
ExecStart=/usr/bin/node index.js
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

Restart=always starts the bot again after a crash or a process.exit(1). Stop any other copy first, or Telegram answers both with 409 Conflict.

Self-hosting or a bot plan

On a VPS you manage yourself:

  • Operating system updates and security, and Node.js upgrades.
  • Running npm install after each change, 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 Node.js version picker, and npm install when package.json, the lock file or the Node.js version change.
  • The token, checked with Telegram before it is saved, stored encrypted and given to your code.
  • Starting the bot again after a crash. If it crashes again within a minute, it stays stopped and Console says why.
  • Banners above Console that name the cause when Telegram refuses the token, another copy is running or a webhook is set, with a Remove webhook button on the Telegram pane.
  • Upload, Git deploys and backups from the panel.

See also How to use a MySQL database with a grammY bot (mysql2).

Questions people ask

Is this the same as the panel's starter bot?

Close to it. On a Node.js server, Create a starter bot writes index.js and package.json with grammY ^1.46.0: it answers /start with your first name, prints Logged in as @your_bot, catches errors with bot.catch and prints a plain line when Telegram refuses the token. Change it as you like, then restart.

Do I have to remove my webhook before bot.start()?

No. bot.start() removes it with deleteWebhook before it polls, and the messages that were waiting are kept. If your old code used webhookCallback(bot, ...) with a web server, replace that with bot.start(): these plans have no public HTTPS address for a webhook yet.

Is bot.start() enough for a busy bot?

For most bots, yes. bot.start() handles messages one after another; grammY's own notes say it suits up to about 5,000 messages an hour, and point to the @grammyjs/runner package for more, or for commands that take a long time, such as large file transfers. The runner also uses long polling, so it works on our plans.

Can I run grammY on Bun or Deno instead of Node.js?

Yes. When you order, pick Bun or Deno instead of Node.js: our Telegram bot hosting page lists grammY for both. The steps on this page are for Node.js.

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

Already hosting your bot somewhere else? Move it to us

OUR LATEST BLOGS