A bot on your laptop stops when the lid closes, and a free tier puts it to sleep while messages pile up unanswered. Telegram and Discord bot hosting on a VPS gets around both: the bot lives on a small Linux server that never sleeps, systemd starts it at boot and after every crash, and its token sits in a file only root and the bot can read. Follow along and you end up with two working bots on one Debian 13 server, one in Python for Telegram and one in Node.js for Discord.
If you only read this:
- A long-polling Telegram bot and a Gateway Discord bot only connect outward, so they need no domain and no open port.
- Telegram keeps uncollected updates for 24 hours at most.
- Run each bot as its own user under systemd, with a restart delay and a start limit.
- Keep tokens in a root-owned env file, out of code and logs.
- Webhooks need HTTPS on port 443, 80, 88 or 8443, over IPv4.
Is Telegram and Discord bot hosting on a VPS worth it over a free tier?
For any bot people rely on, yes, because both platforms punish a bot that comes and goes. Per the Telegram Bot API documentation, pending updates are kept for 24 hours at most. A Discord bot hears nothing unless its Gateway connection, the WebSocket Discord pushes events through, is open. Free hosting used to hide this, until Heroku ended free dynos in November 2022 and Glitch stopped hosting projects in July 2025; most remaining free tiers put idle apps to sleep.
A VPS fixes this the plain way: a process that keeps running until you stop it. Your own IP helps too. Discord temporarily blocks any IP that sends 10,000 invalid requests (401, 403 or 429 responses) within 10 minutes, per its rate limit documentation, so on a shared-IP platform a neighbour's broken bot can spend that budget for you.
Is 1 GB of RAM enough for a Telegram or Discord bot?
Yes, with room to spare. Telegram, Discord, python-telegram-bot and discord.js publish no memory figure, so we measured: on a Debian 13 test server, systemctl status showed under 50 MB for each bot from this guide right after setup. The operating system needs more than the bots do; Debian's installation guide recommends 1 GB of RAM for a server without a desktop. Memory grows mainly on Discord, where discord.js caches servers, members and messages, so check again once your bot is busy.
Chat messages reach a bot through Telegram's and Discord's servers, never straight from your users, so any of our three cities works. RS Computers runs its KVM virtual machines in Amsterdam (Netherlands), Prishtina (Kosovo) and Dublin (Ireland). Stock per city changes, so check the VPS plans page before you pick one. For a bot, the parts that matter are IPv4 and IPv6 addresses of its own, NVMe storage for its database, unmetered traffic on a 1 Gb/s (VPS) or 10 Gb/s (VDS) port, and free weekly backups of the whole server that you restore from the client area. When a bot outgrows its plan, you upgrade there too, with a short reboot on the same server. You pick the operating system at order time: Linux on any plan, or Windows Server on VPS Mini and every VDS plan.
| Plan | Runs |
|---|---|
| VPS Nano (1 vCPU, 1 GB, 20 GB NVMe, 1 Gb/s) | Both bots from this guide on long polling. |
| VPS Micro (2 vCPU, 2 GB, 40 GB NVMe, 1 Gb/s) | Webhooks behind Caddy, several bots, or a bot with a small PostgreSQL or Redis database. |
| VPS Mini (4 vCPU, 4 GB, 80 GB NVMe, 1 Gb/s) | Bots that render images or PDFs, Mini App backends, or bots that need Windows-only tools. |
| VDS Small (4 vCPU, 8 GB, 240 GB NVMe, 10 Gb/s) | A discord.js bot in thousands of servers, or a self-hosted Telegram Bot API server for files over 50 MB. |
These are estimates, not published requirements.
Prepare Debian 13
Order a plan with Debian 13 and log in over SSH as root. Every block below runs as root (on Ubuntu 24.04, if you log in as a normal user, run sudo -i first); the bot users have no login shell, so their commands go through runuser. If SSH itself is new, day 1 of our beginner's Linux lab on a VPS covers logging in from Windows, a Mac or a phone.
Step 1: update and close the firewall
First, update the system, add a few tools and switch on ufw, the firewall, with only SSH allowed in.
# as root
apt update && apt upgrade -y
apt install -y curl ca-certificates gpg nano ufw
ufw allow 22/tcp
ufw --force enable
ufw status
The last command prints Status: active and 22/tcp ALLOW Anywhere. If SSH listens on another port, change the 22 before pasting.
On Debian 13, logs live in the journal rather than /var/log/syslog, and journalctl reads them.
Bot one: a Python Telegram bot on the VPS
Step 2: get a token from BotFather
In Telegram, open @BotFather, Telegram's bot for making bots, send /newbot, and pick a name and a username ending in bot. It replies with a token like 110201543:. Whoever holds the token controls the bot.
Step 3: a user and a virtual environment
Debian refuses pip install into the system Python, so the bot gets a virtual environment (venv), a private folder of packages. Below, a tgbot user without a login shell gets python-telegram-bot 22.8 in a venv of its own.
# as root
apt install -y python3 python3-venv
useradd --system --create-home --home-dir /opt/tgbot --shell /usr/sbin/nologin tgbot
echo "python-telegram-bot==22.8" > /opt/tgbot/requirements.txt
runuser -u tgbot -- python3 -m venv /opt/tgbot/venv
runuser -u tgbot -- /opt/tgbot/venv/bin/pip install -r /opt/tgbot/requirements.txt
runuser -u tgbot -- /opt/tgbot/venv/bin/python -c "import telegram; print(telegram.__version__, telegram.__bot_api_version__)"
The last line prints 22.8 10.0, the library version and the Bot API version it speaks (10.0, released 8 May 2026). Telegram has moved on since: its Bot API changelog reached 10.3 on 24 August 2026, adding rich messages and messages in groups that only one member can see, and python-telegram-bot 22.8 has no methods for those yet. Commands, replies and webhooks work the same. If you see ensurepip is not available, install python3-venv, delete /opt/tgbot/venv and repeat. An externally-managed-environment error means you ran the system pip; never answer it with --break-system-packages.
Step 4: the token goes into an env file
An env file holds NAME=value lines that systemd loads into the bot's environment. Create one that only root and the tgbot group can read; the last line opens it in nano.
# as root
mkdir -p /etc/tgbot
install -m 640 -o root -g tgbot /dev/null /etc/tgbot/tgbot.env
nano /etc/tgbot/tgbot.env
Type TELEGRAM_BOT_TOKEN= and the token, with no spaces or quotes. Ctrl+O and Enter save (nano shows Wrote 1 line), and Ctrl+X closes.
Step 5: write the bot
The bot answers /start and /uptime, and its webhook branch stays off until the env file says otherwise. Before writing the bot, ask Telegram's getMe whether the token works; the block then writes bot.py and checks its syntax.
# as root
. /etc/tgbot/tgbot.env && curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe"; echo
cat > /opt/tgbot/bot.py <<'EOF'
import logging
import os
import time
from telegram.ext import Application, CommandHandler
logging.basicConfig(level=logging.INFO)
# keep the token out of the logs
logging.getLogger("httpx").setLevel(logging.WARNING)
STARTED = time.monotonic()
async def uptime(update, context):
minutes = int(time.monotonic() - STARTED) // 60
await update.effective_message.reply_text(f"Awake for {minutes} minutes.")
app = Application.builder().token(os.environ["TELEGRAM_BOT_TOKEN"]).build()
app.add_handler(CommandHandler(["start", "uptime"], uptime))
domain = os.environ.get("TG_WEBHOOK_DOMAIN")
if domain: # webhook mode behind Caddy (step 15)
path = os.environ["TG_WEBHOOK_PATH"]
app.run_webhook(
listen="127.0.0.1",
port=8080,
url_path=path,
secret_token=os.environ["TG_WEBHOOK_SECRET"],
webhook_url=f"https://{domain}/{path}",
)
else: # long polling: no domain, no open port
app.run_polling()
EOF
chown tgbot:tgbot /opt/tgbot/bot.py
runuser -u tgbot -- /opt/tgbot/venv/bin/python -m py_compile /opt/tgbot/bot.py && echo "bot.py OK"
Expect JSON starting {"ok":true with your bot's username, then bot.py OK; "error_code":401 means a wrong token, and 404 usually means the colon in the token went missing. If the prompt turns into > and waits, type EOF and press Enter. Keep the httpx line: python-telegram-bot's own examples have it, because without it your token lands in the journal with every request.
Step 6: keep it running 24/7 with systemd
systemd is Linux's service manager, and a unit file tells it how to run, restart and sandbox a program. The unit below starts the bot now and at every boot; the status check waits five seconds so the bot has time to log in.
# as root
cat > /etc/systemd/system/tgbot.service <<'EOF'
[Unit]
Description=Telegram bot
Wants=network-online.target
After=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=exec
User=tgbot
WorkingDirectory=/opt/tgbot
EnvironmentFile=/etc/tgbot/tgbot.env
ExecStart=/opt/tgbot/venv/bin/python /opt/tgbot/bot.py
Restart=always
RestartSec=10
StateDirectory=tgbot
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now tgbot
sleep 5
systemctl status tgbot --no-pager
Look for Active: active (running) and Application started, then send /uptime: the bot answers Awake for 0 minutes. journalctl -u tgbot -f follows the log live. If the status says activating (auto-restart) instead, the lines under it name the problem; InvalidToken means the token in the env file is wrong.
Why Restart=always? The systemd service manual recommends on-failure, but a clean exit with code 0 is no failure to systemd, and an old discord.js bug made bots exit exactly that way. Timing matters more. RestartSec defaults to 100 ms and systemd allows 5 starts in 10 seconds, so a bot that crashes at start burns its retries within seconds and stays dead; our Telegram bot with a wrong token used all five in under three seconds. Ten seconds apart, five tries per five minutes, is calmer. Once a unit has given up, systemd also refuses a manual start until those five minutes have passed, so after fixing the cause run systemctl reset-failed tgbot and then systemctl restart tgbot. ProtectSystem=strict makes the disk read-only for the bot except /var/lib/tgbot, which StateDirectory creates.
If you see Conflict: terminated by other getUpdates request in the log, another copy is polling with the same token, often a test run in a second SSH window. Stop that copy.
Bot two: a discord.js bot with a slash command
Step 7: Node.js 24 from NodeSource
Skip the distribution's Node.js: Debian 13 ships version 20, which reached its upstream end of life on 30 April 2026, and Ubuntu 24.04 ships 18, which ended a year before that. nvm installs per user, which is awkward for a service, so this block installs Node.js 24 LTS from NodeSource's repository. The Node.js release schedule keeps 24 in support until 30 April 2028, while Node.js 26 becomes LTS only on 28 October 2026, so there is no reason to jump yet.
# as root
curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh
bash nodesource_setup.sh
apt install -y nodejs
node -v
npm -v
Expect v24.21.0 or a later 24.x, then the npm version. Never add Debian's own npm package; NodeSource's nodejs already includes it.
Step 8: create the application in the Developer Portal
- At discord.com/
developers/ , create a New Application. Its Application ID is yourapplications CLIENT_ID. - On the Bot page, click Reset Token and copy the token, shown only once.
- Under OAuth2, URL Generator, tick
botandapplications.commands, then open the link to add the bot to your server. - With Developer Mode on (Settings, Advanced), right-click your server and copy its ID, your
GUILD_ID.
Step 9: a user, discord.js and the env file
Next come the dcbot user, a one-line npm safety setting, the newest discord.js 14 release (14.27.0, published 15 July 2026) and an env file.
# as root
useradd --system --create-home --home-dir /opt/dcbot --shell /usr/sbin/nologin dcbot
cd /opt/dcbot
echo "min-release-age=7" > /opt/dcbot/.npmrc
runuser -u dcbot -- npm init -y
runuser -u dcbot -- npm install discord.js@14
runuser -u dcbot -- npm ls discord.js
mkdir -p /etc/dcbot
install -m 640 -o root -g dcbot /dev/null /etc/dcbot/dcbot.env
nano /etc/dcbot/dcbot.env
Type three lines, DISCORD_TOKEN=, CLIENT_ID= and GUILD_ID=, each with its value, then save and close. The npm ls output above shows discord.js@14.27.0 or a later 14.x. The .npmrc line sets npm's min-release-age: npm only installs versions published at least seven days earlier, so a freshly poisoned release cannot reach the bot in its first week. npm 11.10 and newer understand it, and Node.js 24 brings npm 11.19. If npm reports vulnerabilities, run runuser -u dcbot -- npm audit fix from /opt/dcbot, but never add --force, which may jump to another major version. If npm warns that a package was left at a vulnerable version because a fix is newer than the release-age cutoff, that fix is under seven days old; run the command again next week.
Step 10: the bot and its slash command
A Discord bot declares gateway intents, the event types it wants, when it connects. GUILD_MEMBERS, GUILD_PRESENCES and MESSAGE_CONTENT are privileged, and without MESSAGE_CONTENT, server messages arrive with empty text, which broke many old !command bots. Slash commands need only the Guilds intent. This block checks the token, writes the bot and a script that registers /ping, and tests both.
# as root
. /etc/dcbot/dcbot.env && curl -s -H "Authorization: Bot ${DISCORD_TOKEN}" https://discord.com/api/v10/users/@me; echo
cat > /opt/dcbot/index.js <<'EOF'
const { Client, Events, GatewayIntentBits } = require('discord.js');
// no privileged intents needed
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, (c) => console.log(`Ready! Logged in as ${c.user.tag}`));
// log a failed reply instead of crashing the whole bot
client.on(Events.Error, (error) => console.error(error));
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand() || interaction.commandName !== 'ping') return;
const minutes = Math.floor(process.uptime() / 60);
await interaction.reply(`Pong! Awake for ${minutes} minutes.`);
});
client.login(process.env.DISCORD_TOKEN);
EOF
cat > /opt/dcbot/deploy-commands.js <<'EOF'
const { REST, Routes } = require('discord.js');
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
rest.put(Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID), {
body: [{ name: 'ping', description: 'Replies with Pong and the uptime' }],
})
.then(() => console.log('Registered commands'))
.catch(console.error);
EOF
chown dcbot:dcbot /opt/dcbot/index.js /opt/dcbot/deploy-commands.js
node --check /opt/dcbot/index.js && node --check /opt/dcbot/deploy-commands.js && echo "JS OK"
Discord answers with JSON about your bot, including "bot": true, and the checks end with JS OK. A 401: Unauthorized reply points to a wrong or reset token.
Step 11: register /ping and start the service
Register the command once, then give the Discord bot a unit shaped like the Telegram one.
# as root
cd /opt/dcbot
runuser -u dcbot -- node --env-file=/etc/dcbot/dcbot.env deploy-commands.js
cat > /etc/systemd/system/dcbot.service <<'EOF'
[Unit]
Description=Discord bot
Wants=network-online.target
After=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=exec
User=dcbot
WorkingDirectory=/opt/dcbot
EnvironmentFile=/etc/dcbot/dcbot.env
ExecStart=/usr/bin/node /opt/dcbot/index.js
Restart=always
RestartSec=10
StateDirectory=dcbot
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now dcbot
sleep 5
systemctl status dcbot --no-pager
You see Registered commands, active (running) and Ready! Logged in as. Type /ping in your server and the bot answers with its uptime. Rerun the deploy script only when commands change; Discord allows 200 command creates per day per server.
If you see Used disallowed intents in journalctl -u dcbot, enable that intent in the portal or remove it from the code; Discord closes such connections with code 4014 for good. "The application did not respond" means the bot took over 3 seconds, and the journal then shows DiscordAPIError[10062]: Unknown interaction. The Events.Error line in index.js logs that error; without it, one late reply crashes the whole bot. For slow work, call interaction.deferReply() first, which buys 15 minutes.
Telegram webhook or long polling?
With long polling, the bot calls getUpdates and Telegram holds the request open until something arrives. With a webhook, Telegram sends each update to an HTTPS URL you register with setWebhook. You cannot use both at once.
| What differs | Long polling | Webhook |
|---|---|---|
| Who connects | Your bot, outbound | Telegram, inbound to your server |
| Inbound port | None | 443, 80, 88 or 8443 |
| Certificate | Not needed | HTTPS with TLS 1.2 or newer |
| IP version | Any | IPv4 only |
| Parallel delivery | One request at a time | Up to 100 connections, 40 by default |
On a server that never sleeps, stay with polling; the python-telegram-bot wiki says you need a good reason before switching to a webhook. Good reasons: Caddy already runs there, or a Mini App needs HTTPS anyway.
Running the Telegram webhook behind Caddy
Telegram's webhook guide sets the rules: HTTPS with TLS 1.2 or newer, a certificate matching the domain, port 443, 80, 88 or 8443, IPv4 only, and requests from 149.154.160.0/20 and 91.108.4.0/22. Caddy, a reverse proxy that fetches its own certificates, handles HTTPS and passes requests to the bot on 127.0.0.1:8080. Our Caddy, Nginx and Traefik comparison covers the alternatives.
Step 12: DNS and ports
Point an A record for your domain (YOUR_DOMAIN below) at the server's IPv4 address; this block checks it and opens ports 80 and 443.
# as root
getent ahostsv4 YOUR_DOMAIN
ufw allow 80/tcp
ufw allow 443/tcp
getent prints your IPv4 address (if not, give DNS a few minutes), and ufw answers Rule added.
Step 13: install Caddy
Debian 13 and Ubuntu 24.04 package Caddy 2.6.2 from 2022, so install the current release from Caddy's own repository instead.
# as root
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
chmod o+r /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install -y caddy
caddy version
The last line prints v2.11.7 or newer.
Step 14: point Caddy at the bot
Three lines of Caddyfile forward your domain to the bot, and Caddy checks them before the reload.
# as root
cat > /etc/caddy/Caddyfile <<'EOF'
YOUR_DOMAIN {
reverse_proxy 127.0.0.1:8080
}
EOF
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy
Caddy should answer Valid configuration (a formatting warning is harmless), and within a minute journalctl -u caddy shows certificate obtained successfully.
Step 15: switch the bot to webhook mode
The webhook needs one extra package, plus your domain and a random path and secret in the env file; after a restart, getWebhookInfo shows whether Telegram accepted it.
# as root
echo "python-telegram-bot[webhooks]==22.8" > /opt/tgbot/requirements.txt
runuser -u tgbot -- /opt/tgbot/venv/bin/pip install -r /opt/tgbot/requirements.txt
echo "TG_WEBHOOK_DOMAIN=YOUR_DOMAIN" >> /etc/tgbot/tgbot.env
echo "TG_WEBHOOK_PATH=tg-$(openssl rand -hex 16)" >> /etc/tgbot/tgbot.env
echo "TG_WEBHOOK_SECRET=$(openssl rand -hex 32)" >> /etc/tgbot/tgbot.env
systemctl restart tgbot
sleep 5
. /etc/tgbot/tgbot.env && curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getWebhookInfo"; echo
The JSON shows your URL, "pending_update_count":0 and no last_error_message, and /uptime now answers through Caddy. python-telegram-bot rejects any request without the secret header. A 502 Bad Gateway comes from Caddy when it cannot reach the bot, and IPv6-only addresses are not allowed tells you the A record is missing. To return to polling, delete the three TG_WEBHOOK_ lines and restart.
Which Telegram and Discord limits will a growing bot hit?
Telegram's bot FAQ asks for about one message per second per chat, 20 per minute per group and about 30 per second overall; beyond that comes 429 Too Many Requests: retry after N. Bots upload files up to 50 MB and download up to 20 MB. Privacy mode is on by default, so in groups a bot sees only commands and replies to it; after /setprivacy in BotFather, remove the bot from the group and add it again.
On Discord, you switch privileged intents on yourself in the Developer Portal until the bot gets big. Since 10 June 2026, per Discord's privileged intent review page, an app that more than 10,000 unique users can see across all its servers must apply for that access and renew it every year. Growing past 100 servers is a separate step that needs a verified bot. The REST API allows 50 requests per second, and since 3 September 2026 a bot may upload files of up to 20 MiB, twice the old default, per Discord's developer change log. The Gateway documentation allows 1000 IDENTIFY calls, the login that opens a session, per 24 hours; beyond that, Discord ends all sessions, resets the token and emails the owner. Every fresh process costs one, so a bot crashing right after login, with a 5-second restart delay and no start limit, would burn the budget in about 83 minutes. Our units stop after five tries. From 2,500 servers, a bot must shard, splitting its servers across several connections.
Updating the libraries and backing up the bots
Five steps cover every update:
- Confirm a recent backup exists.
- Run
apt update && apt upgrade, which updates Caddy too. - Python: read the changelog, edit the version in
requirements.txt, rerun the pip line from step 3 and restart. Roll back the same way. - Node: stay on major version 14 (
npm install discord.js@^14as dcbot), because discord.js 15 is still published only as development builds. Keeppackage-lock.json, deploy withnpm ciand restart. - Check
systemctl statusand send/uptimeor/ping.
Lockfiles matter: in September 2025 the Shai-Hulud worm compromised more than 500 npm packages, according to CISA, and in August 2026 its ChainDrop variant hit over 1,300 package versions, keyv among them, according to Singapore's Cyber Security Agency. The lockfile and the seven-day rule from step 9 make it much harder for a release like that to reach your server. Never run a second copy with the production token either. Telegram answers 409 Conflict and Discord gets double replies, so test with a separate bot.
For a copy you control between the free weekly backups, archive both bots, leaving out the folders that rebuild themselves.
# as root
tar -czf /root/bots-$(date +%F).tar.gz --exclude=venv --exclude=node_modules --exclude=.cache --exclude=.npm /opt/tgbot /opt/dcbot /etc/tgbot /etc/dcbot /etc/systemd/system/tgbot.service /etc/systemd/system/dcbot.service /var/lib/tgbot
ls -lh /root/bots-*.tar.gz
tar's note about leading slashes is harmless, and ls shows a small archive. It contains your tokens, so store it encrypted off the server; our guide to off-site backups with restic automates that. systemd knows a bot is running, not that it answers, so add an outside check with Uptime Kuma or Grafana, which both alert to Telegram and Discord.
Keeping the token and the server safe
If a token leaks, replace it at once with /token in BotFather or Reset Token on Discord's Bot page. GitGuardian counted 28.65 million new hardcoded secrets in public GitHub commits in 2025, so tokens stay under /etc, away from any repository. Also:
- Switch SSH to keys, then turn off password logins once your key works. Our Debian 13 image ships with root password logins switched on, and the guessing does not wait: a fresh test server we booted logged 261 failed SSH logins from 7 addresses in its first 78 minutes. Week 2 of our Linux lab guide walks through the change.
- One user per bot, never root.
- Deny inbound traffic by default; ports 8080 and 2019 (Caddy's admin API) stay on localhost.
- Check the sender's user ID before admin commands.
- Give Discord bots the smallest permissions that work, never Administrator.
What to build on this server
Some jobs need no always-on process: for one-way alerts, a Discord channel webhook or one curl to Telegram's sendMessage from cron is enough. Those alerts need your chat ID, the number Telegram gives your conversation with the bot. Stop the bot with systemctl stop tgbot so it does not collect the next message, send the bot any text, then run this block:
# as root
. /etc/tgbot/tgbot.env && curl -s "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getUpdates"; echo
systemctl start tgbot
The number after "chat":{"id": is your chat ID; a group's is negative. An empty "result":[] means the bot was still running or no message has arrived yet. In webhook mode Telegram refuses getUpdates with a 409 error, so look the ID up before step 15, or return to polling for a moment as the end of step 15 describes. The price and tender monitors in our web scraping guide send their alerts with this chat ID and sendMessage.
Bots earn their keep when they talk back. Say you run a phone repair shop in Prishtina: through Telegram Business, a bot can answer "is my repair ready?" at 2 a.m. from a PostgreSQL database on the same VPS. Since Bot API 10.0, a business bot can manage the shop's account even without a Telegram Premium subscription. A bot that passes questions to a language model fits on VPS Nano as long as the model runs elsewhere; our guide to AI apps on a VPS without a GPU covers what changes when it runs on your own server. On Discord, ticket and role bots are the classic, with a status bot for a self-hosted game server close behind. If your bot delivers email straight to other mail servers rather than through a mail provider, outbound port 25 is blocked on a new RS Computers VPS; ask us on Telegram to open it.
Frequently asked questions
How do I keep my Discord bot online 24/7?
Run it on an always-on server as a systemd service with Restart=always, a restart delay and a start limit, as in step 11. screen and tmux survive a closed SSH window but not a reboot.
Do Telegram bots need a server?
Yes. Telegram only relays messages, and updates nobody collects expire after 24 hours. With long polling the server needs no domain, certificate or open port, so the smallest VPS is enough.
Which ports can a Telegram webhook use?
Only 443, 80, 88 and 8443, over HTTPS with TLS 1.2 or newer, and only on IPv4. A long-polling bot needs no inbound port.
Should I use PM2 or systemd for a Node.js Discord bot?
systemd. It is already there, starts the bot at boot, keeps logs in the journal and adds sandboxing and start limits. PM2 is one more process to keep alive.
Can I run several bots on one VPS?
Yes: one user, env file and unit per bot, and one running copy per token. Discord counts invalid requests per IP, so one misbehaving bot can get every bot on that address blocked for a while.
Can RS Computers set this up for me?
Yes. Message us on Telegram or email info@rscomputers-ks.com with what the bot does, and we will suggest a plan and quote the setup.
Get the Telegram bot answering first
Order VPS Nano with Debian 13 on the VPS and VDS plans page and work through steps 1 to 6. The Telegram bot should answer /uptime within the hour; the Discord bot takes another twenty minutes. Leave webhooks for the day you need one.