Host a Telegram Bot on a VPS
A Telegram bot needs 1 vCPU and 1 GB of RAM. Install Python 3 on Ubuntu 24.04, run the bot under a systemd service so it restarts after a crash, and start with long polling. Add an nginx webhook with a Let's Encrypt certificate only when you need inbound HTTPS.
For developers deploying a Python Telegram bot on an Ubuntu VPS with systemd.
knowledge
- Read time
- 6 min
- Sections
- 7
- Updated
- 14 Nov 2026
- Written by
- WebJi® team
Last editorial update: 15 November 2026.
What a Telegram bot needs from a VPS
A Telegram bot is a long-running process that talks to the Bot API over HTTPS, and it needs one CPU core, about a gigabyte of RAM and a supervisor that restarts it — nothing more. That is why a cheap VPS beats a container platform for anything you want to keep online for years.
The Bot API is plain 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 the URL you publish, so you need a domain, a certificate and an inbound port. Starting on polling and moving to a webhook later 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, already on the server, no extra package
WebJi® sells these as VPS servers with root access from ₹499 a month before 18% GST, each configuration quoted individually. A bot does not need the biggest 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 current tiers on the pricing page before choosing. 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 so everything the bot needs lives in that user's home directory, not root's.
- In Telegram, message @BotFather, send
/newbot, choose a name and the 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, adding 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 code with python-dotenv. - Run the bot by hand once:
python bot.py. Send it /start from your phone and confirm it replies before automating anything.
This step worked when the bot answers /start while you are still in the SSH session, and ls -l .env shows permissions of -rw-------.
Run the bot as a systemd service
Run the bot under a systemd unit with Restart=always so it survives crashes and reboots — the restart line is the point of the exercise.
# /etc/systemd/system/tgbot.service
[Unit]
Description=Telegram bot
After=network-online.target
[Service]
User=botuser
WorkingDirectory=/home/botuser/bot
EnvironmentFile=/home/botuser/bot/.env
ExecStart=/home/botuser/bot/venv/bin/python bot.py
Restart=always
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target
- Run
sudo systemctl daemon-reload, thensudo systemctl enable --now tgbot. - Check state with
systemctl status tgbotand follow output withjournalctl -u tgbot -f. - Kill the process deliberately with
sudo systemctl kill tgbot; it should be back within five seconds, proving the restart policy works.
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. A screen or tmux session survives a disconnect but not a reboot, and it will not restart the bot after a crash.
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. The Telegram API treats the two update mechanisms as mutually exclusive: getUpdates will not work while a webhook is set, which is why running both produces a 409 Conflict and neither mode works.
| Property | Long polling | Webhook |
|---|---|---|
| Inbound ports | None beyond SSH | 443 (or 80, 88, 8443) 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 |
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, passing 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.telegram.org/bot$BOT_TOKEN/getWebhookInfo"— the URL should match and last_error_message should be empty.
Put the token in the webhook path as well as the URL you publish: a bare /webhook endpoint collects junk requests from scanners within hours. Better still, setWebhook accepts a secret_token that Telegram delivers in the X-Telegram-Bot-Api-Secret-Token header on every update, which your bot can verify and reject everything else.
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, allow 80 and 443 only if you run a webhook, thensudo ufw enable - Token: kept in .env with 600 permissions, never in the repository, never in a screenshot or 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 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. The same discipline applies to any long-running service; our sudo user setup on Ubuntu covers the account side.
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 — and all four are visible in the journal within a minute.
- Nothing in the chat:
journalctl -u tgbot -n 50and look for a startup exception, usually a missing environment variable. - 409 Conflict: a second copy is running. Stop the duplicate, or clear the webhook if you switched back 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 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 an integration. Never run both at once, or you get a 409 Conflict — the API treats the two update paths as mutually exclusive.
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.
Can I host a Telegram bot on shared hosting?
Usually not. Shared hosting runs PHP requests and kills long-running Python processes, 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.
Last editorial update: 15 November 2026. Prices follow the dated sources on this page; checkout confirms current charges.
Ready to get started with WebJi®?
Join 12,000+ businesses across India and overseas. INR pricing, 18% GST shown before payment and 24/7 email support in Hindi and English — average first reply under 60 minutes.