What a Telegram bot needs from a VPS
A bot is a long-running process that talks to the Telegram Bot API over HTTPS. It needs a small Linux server, one CPU core, about a gigabyte of RAM, and a supervisor that restarts it. That is the whole requirement, and it is why a cheap VPS beats a container platform for anything you want to keep online for years.
The Bot API is HTTP. In long polling mode your bot opens an outbound request and waits for updates, so the server needs no open ports except SSH. In webhook mode Telegram pushes each update to a URL you publish, so you need a domain, a certificate and an inbound port. Starting on polling, then moving to a webhook, is the order that avoids configuring HTTPS before the bot even answers.
- OS: Ubuntu 24.04 LTS, which ships Python 3.12.
- Access: SSH with a key, not a password, and an unprivileged user for the bot.
- Runtime: Python 3 with a virtual environment, or Node.js with a pinned version.
- Supervisor: systemd, which is already on the server and needs no extra package.
WebJi® sells these as VPS servers with root access, and the tiers are on the pricing page. A bot does not need the smallest plan to be fast; it needs the plan to be stable.
Pick the VPS size: 1 vCPU and 1 GB is enough
One core and 1 GB of RAM runs a text bot for thousands of daily users. Buy memory for the work the bot does, not for the number of people who might message it.
| Workload | RAM | What pushes it up |
|---|---|---|
| Commands, menus, small database | 1 GB | Nothing much; this is the common case |
| Image or PDF processing | 2 GB | Libraries that load whole files into memory |
| Local speech or vision model | 4 GB and up | Model size, not traffic |
| Many concurrent jobs or a queue worker | 2 GB | Workers, Redis, a second process |
Size the disk at 20 GB or more if you keep logs and media, and check the current VPS tiers on the pricing page before you choose. Avoid 512 MB for the build step even if the bot would fit afterwards. Installing Python packages with a C extension on 512 MB regularly runs out of memory, and the failure looks like a broken requirement rather than a small server.
Create the bot and get the code onto the server
Get a token from BotFather, then prepare the server as an unprivileged user. Everything the bot needs lives in that user's home directory, not in root's.
- In Telegram, message @BotFather, send /newbot, choose a name and a username ending in bot, and copy the token it returns.
- Connect with SSH, then run
sudo adduser --disabled-password botuserandsudo su - botuser. - Install the toolchain:
sudo apt update && sudo apt install -y python3-venv python3-pip git. - Clone the repository, then create and activate the environment:
python3 -m venv venv && source venv/bin/activate. - Install pinned dependencies with
pip install -r requirements.txt, and add python-telegram-bot if the project does not list it. - Create the environment file:
printf 'BOT_TOKEN=your-token\n' > .env && chmod 600 .env, and read it in the code with python-dotenv. - Run the bot by hand once:
python bot.py. Send it /start from your phone and confirm it replies before you automate anything.
You know this step worked when the bot answers /start while you are still in an SSH session, and when ls -l .env shows permissions of -rw------- rather than -rw-r--r--.
Run it forever with a systemd unit
systemd restarts the bot when it crashes, starts it at boot, and writes the logs to the journal. A screen session or an ampersand at the end of a command does none of those.
- Create the unit as root:
sudo nano /etc/systemd/.system/tgbot. service - Paste a unit with Description, After=network-online.target, User=botuser, WorkingDirectory, EnvironmentFile pointing at the .env, and ExecStart pointing at the venv Python and bot.py.
- Add Restart=always, RestartSec=5, NoNewPrivileges=true and PrivateTmp=true. The restart line is the point of the exercise.
- Run
sudo systemctl daemon-reload, thensudo systemctl enable --now tgbot. - Check the state with
systemctl status tgbotand follow the output withjournalctl -u tgbot -f. - Kill the process deliberately with
sudo systemctl kill tgbot. It should be back within five seconds, which proves the restart policy works.
You know it worked when status shows active (running), the journal shows your startup line, and a message to the bot is answered after a reboot with no one logged in.
Long polling or webhook: which to run
Start with long polling and move to a webhook when you need faster delivery, inbound integrations or a fixed URL. Both are supported by every major library, and switching is a code change plus one API call.
| Long polling | Webhook | |
|---|---|---|
| Inbound ports | None beyond SSH | 443 open to Telegram |
| Needs a domain and certificate | No | Yes, HTTPS is mandatory |
| Latency | One request round trip per poll | Update pushed immediately |
| Behind a proxy or NAT | Works anywhere | Needs a public URL |
| Failure mode | Silent stall if the loop dies | Telegram retries; a 500 shows in getWebhookInfo |
Run one mode at a time. Starting the bot in polling while a webhook is set produces a 409 Conflict error and neither mode works, which is the most common mistake after a migration.
Add a webhook with nginx and a free certificate
A webhook needs three things in order: a DNS record, a TLS certificate, and a proxy that forwards Telegram's POST to your bot's local port.
- Point a subdomain such as bot.yourdomain.com at the server's IP with an A record, and wait for it to resolve.
- Run
sudo apt install -y nginx certbot python3-certbot-nginx, thensudo certbot --nginx -d bot.yourdomain.com. - In the nginx site config, add a location block that proxies to 127.0.0.1:8443 with proxy_pass and passes the Host and X-Forwarded-For headers.
- Set the webhook:
curl "https://api..telegram.org/ bot$BOT_TOKEN/ setWebhook?url= https://bot. yourdomain. com/$BOT_TOKEN" - Check it with
curl "https://api.: the URL should match and last_error_message should be empty.telegram.org/ bot$BOT_TOKEN/ getWebhookInfo"
Put the token in the webhook path as well as the URL you publish. Telegram recommends a secret path, and a bare /webhook endpoint collects junk requests from scanners within hours.
Lock down the server and the token
A bot token is a password that controls the bot. Leaked, it lets anyone read updates, send messages as your bot and change its commands.
- Firewall:
sudo ufw allow OpenSSH, then allow 80 and 443 only if you run a webhook, thensudo ufw enable. - Token: kept in the .env with 600 permissions, never in the repository, never in a screenshot or a support ticket.
- User: the service runs as botuser, not root, so a library bug cannot rewrite system files.
- Updates:
sudo apt install unattended-upgradeshandles security patches; upgrade the bot's own dependencies on a schedule you choose. - Revoke: if the token was exposed, send /revoke in BotFather and update the .env. The old token dies immediately.
A bot that only uses polling does not need ports 80 or 443 open at all. If you are not serving a webhook or a health page, leave them closed.
Logs, updates and the failures worth knowing
Most bot outages are one of four things: the process died, the token is wrong, two instances are polling, or the server ran out of memory. All four are visible in the journal within a minute.
- Nothing in the chat:
journalctl -u tgbot -n 50and look for an exception on startup, usually a missing environment variable. - 409 Conflict: a second copy is running. Stop the duplicate, or clear the webhook if you switched to polling.
- OOM killer: a large file or model exceeded RAM. Resize the VPS rather than adding swap, which only postpones it.
- Silent stop after weeks: check that Restart=always is set and that the service is enabled, not just started.
Schedule a monthly update: activate the venv, run pip list --outdated, upgrade one package at a time, and restart the service. A bot that has not been updated in a year is a bot running a library with known bugs.
Frequently Asked Questions
How much RAM does a Telegram bot need on a VPS?
One gigabyte is enough for a text bot handling thousands of daily users. Move to 2 GB only if the bot processes images, PDFs or a local model. Avoid a 512 MB plan, because installing Python packages with C extensions can run out of memory during the build.
Should I use long polling or a webhook?
Start with long polling: it needs no domain, certificate or open inbound port. Move to a webhook when you want lower delivery latency, a fixed URL, or integration with another service. Never run both at once, or you get a 409 Conflict.
How do I keep a Telegram bot running after I close SSH?
Run it as a systemd service with Restart=always and enable it at boot. A screen or tmux session survives a disconnect but not a reboot, and it will not restart the bot after a crash.
Why does my bot show a 409 Conflict error?
Two processes are fetching updates for the same token. Stop the duplicate, and if you just moved from a webhook to polling, clear the webhook with deleteWebhook first. One token supports one active consumer.
Can I host a Telegram bot on shared hosting?
Usually not. Shared hosting runs PHP requests and kills a long-running Python process, so polling cannot stay alive. A VPS with root access, or a container platform that keeps a process running, is the right home for a bot.
What happens if my bot token leaks?
Anyone with the token controls the bot: reading updates, sending messages, changing commands. Revoke it in BotFather with /revoke, write the new token into the .env, and restart the service. Revocation is immediate and cannot be undone.

