To host a teloxide 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 teloxide 0.17.0 bot used about 13 MB of memory. A Rust bot is built on your own computer as one Linux x86-64 program: nothing is compiled on the server. teloxide's Bot::from_env() reads the token from TELOXIDE_TOKEN, a name the panel sets for you. The steps on PlayNHost:
- Build your bot for Linux x86-64 on your computer, as shown under Build it for Linux below.
- On Telegram bot hosting, click Create Server on a plan. In the order window, answer What is your bot for? with Telegram and pick Rust.
- 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 Rust Bot.
- 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.
- 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. - Click Start bot and open Console. After
[PlayNHost] Starting your botyour bot printsLogged in as @your_bot, and the chip on Overview turns to Online in Telegram. - 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 Rust 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 Rust Discord bot. This page is about teloxide: what it uses on a server and what to know before you host it.
What we measured
| Library | Version | Memory at rest | Start to the platform's answer | Program size |
|---|---|---|---|---|
| teloxide (Telegram) | 0.17.0 | 13 MB | 0.1 s | 3.0 MB |
| serenity (Discord) | 0.12.5 | 10 MB | 0.4 s | 13.4 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 Rust Bot type runs, each program called Telegram or Discord with a fake token, which was 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. Both programs were built with stable Rust and cargo build --release on that same image, with pkg-config and libssl-dev added for the build; the teloxide build took 107 s with one CPU, downloads included, so treat that as a rough figure.
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 teloxide program uses a small part of the 0.5 GB STARTER plan.
A minimal teloxide bot
It answers /start, reads the token the panel gives it, prints a ready line and says plainly when Telegram refuses the token. Make a project with cargo new bot and use these two files. The package name bot makes the program bot, the default Entry file.
Cargo.toml
[package]
name = "bot"
version = "0.1.0"
edition = "2021"
[dependencies]
teloxide = { version = "0.17", features = ["macros"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
pretty_env_logger = "0.5"
src/main.rs
use teloxide::{prelude::*, utils::command::BotCommands, ApiError, RequestError};
#[derive(BotCommands, Clone)]
#[command(rename_rule = "lowercase")]
enum Command {
/// Say hello.
Start,
}
async fn answer(bot: Bot, msg: Message, cmd: Command) -> ResponseResult<()> {
match cmd {
Command::Start => {
bot.send_message(msg.chat.id, "Hello! I am running on PlayNHost.").await?;
}
}
Ok(())
}
#[tokio::main]
async fn main() {
// Prints teloxide's errors, such as a 409 Conflict while polling. Without a logger they are silent.
pretty_env_logger::init();
// Reads TELOXIDE_TOKEN, which the panel sets from the token on the Telegram pane.
let bot = Bot::from_env();
match bot.get_me().await {
Ok(me) => println!("Logged in as @{}", me.username()),
Err(RequestError::Api(ApiError::InvalidToken)) => {
println!("Telegram did not accept the bot token. Paste the current one from @BotFather, then restart.");
std::process::exit(1);
}
Err(err) => {
println!("Could not start the bot: {err}");
std::process::exit(1);
}
}
// Long polling: removes a webhook left on the bot, then asks Telegram for messages.
Command::repl(bot, answer).await;
}
The line Logged in as @your_bot is what turns the chip to Online in Telegram. Command::repl runs until the bot is stopped. The code follows the command example in teloxide's own repository; pretty_env_logger is the logger teloxide's examples use.
Build it for Linux
The server runs your program on Debian 13, with glibc 2.41 and OpenSSL 3. A Windows .exe or a macOS build does not run there: 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. Two ways to build:
On Linux, with teloxide's default features
sudo apt install build-essential pkg-config libssl-dev
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo build --release
The program is target/release/bot. teloxide's default features use native-tls, which on Linux links the system's OpenSSL, so the build needs pkg-config and libssl-dev; the server has OpenSSL 3. Build on Debian 12 or 13, or Ubuntu 22.04 or 24.04: their glibc (2.36, 2.41, 2.35 and 2.39) is no newer than the server's. A program built on a newer system, such as Ubuntu 26.04 with glibc 2.43, may ask for functions the server does not have, and then stops at start with an error saying that a GLIBC_ version was not found. The musl build avoids that.
From Windows, macOS or any Linux: a musl build
A musl program is one self-contained file that does not depend on the server's system libraries. Switch teloxide from OpenSSL to rustls, which needs no OpenSSL and carries its own list of trusted certificates. In Cargo.toml, replace the teloxide line with:
teloxide = { version = "0.17", default-features = false, features = ["macros", "ctrlc_handler", "rustls"] }
Keep ctrlc_handler in the list: teloxide only builds repl with that feature. Then build with cargo-zigbuild, which uses Zig as the linker (installing it with pip also installs Zig):
rustup target add x86_64-unknown-linux-musl
pip install cargo-zigbuild
cargo zigbuild --release --target x86_64-unknown-linux-musl
The program lands in target/x86_64-unknown-linux-musl/release/bot. teloxide 0.17 lists Rust 1.82 as its minimum, and the crates it uses can ask for a newer one, so run rustup update stable before you build.
If the token is wrong
In our test, with a fake token, bot.get_me() returned this error (our test program printed it after the error's type):
teloxide::RequestError: A Telegram's error: Invalid bot token
teloxide turns Telegram's Unauthorized answer, and the Not Found a badly formed token gets, into ApiError::InvalidToken. The bot above checks for it before it starts polling, prints Telegram did not accept the bot token. Paste the current one from @BotFather, then restart. and stops. Without that check, Command::repl stops the program with a panic, Couldn't prepare dispatching context: Api(InvalidToken). The panel recognises both and 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 teloxide on a server
- The token name.
Bot::from_env()readsTELOXIDE_TOKENand panics withCannot get the TELOXIDE_TOKEN env variablewhen it is missing. The panel sets it once a token is saved on the Telegram pane, along withTELEGRAM_BOT_TOKEN,BOT_TOKEN,TOKENandTELEGRAM_TOKEN.from_env()also readsTELOXIDE_API_URLandTELOXIDE_PROXYwhen they are set; leave them out to talk to Telegram directly. - No logger, no errors. teloxide reports problems through the
logcrate, so a program without a logger prints nothing when polling fails.pretty_env_logger::init()shows errors only, which is enough: when another copy of the bot polls with the same token, the console gets a line ending inAn error from the update listener: Api(TerminatedByOtherGetUpdates)and the panel shows Your bot is running somewhere else too. For more detail, on the Bot page, pane Environment, add the variableRUST_LOGwith the valueinfo(Add variable) and restart. - Polling never gives up. When asking Telegram for messages fails, teloxide waits and tries again, doubling the wait from 1 second up to 64 seconds, or as long as Telegram asks after
429 Too Many Requests. A second copy of the bot or a token revoked while it runs does not stop the program: it keeps running but gets few or no messages, and only the logged error shows why:Api(TerminatedByOtherGetUpdates)for a second copy,Api(InvalidToken)for a revoked token. See Fix Telegram bot 409 Conflict. - A webhook left on the bot is removed for you.
replandDispatcher::dispatch()start long polling with a 10-second timeout and first calldeleteWebhookwhen a webhook is set, keeping the messages that were waiting. teloxide'swebhooksfeatures need a public HTTPS address, which our plans do not include yet, so usereplordispatch(). - A clean stop. The panel stops a Rust bot with Ctrl+C (
SIGINT).replturns on teloxide's Ctrl+C handler, which stops polling and tells Telegram which messages it has already received, so the next start does not get them again. With your ownDispatcher, add.enable_ctrlc_handler()to the builder, as teloxide's examples do. - Updates. The Rust Bot type has no Packages field and no Git source: your crates 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 Rust there. A program built with the default features needs OpenSSL 3, which Debian 12 and 13 and Ubuntu 22.04 and 24.04 have, and a glibc at least as new as the system you built on. The musl build needs neither.
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 "TELOXIDE_TOKEN=123456789:your-token" > /etc/mybot.env'
sudo chmod 600 /etc/mybot.env
Create /etc/systemd/system/mybot.service:
[Unit]
Description=teloxide 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
KillSignal=SIGINT
[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
KillSignal=SIGINT makes systemctl stop send Ctrl+C, which teloxide's handler catches for a clean stop; systemd's default, SIGTERM, ends the program at once. Restart=always starts the bot again after a crash or an exit. Stop any other copy first, or Telegram answers with 409 Conflict.
Self-hosting or a bot plan
On a VPS you manage yourself:
- Operating system updates and security, and OpenSSL and glibc versions that match your build.
- Copying each new build, the service file, the token file and its permissions.
- Reading
journalctlto 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
TELOXIDE_TOKEN. - 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 or another copy is running, and a Remove webhook button on the Telegram pane.
Questions people ask
Why does my teloxide bot print nothing at all?
teloxide writes through the log crate and prints nothing until a logger is set up. Call pretty_env_logger::init() (or another logger) at the top of main, as the bot above does. Your own println! lines always show in Console.
Do I need teloxide's webhooks feature?
No. A webhook needs a public HTTPS address, which our plans do not include yet. repl and dispatch() use long polling, which needs no address: the bot asks Telegram for new messages. They also remove a webhook left on the bot by an old host.
Can the server compile my Rust code?
No. The Rust Bot type runs a finished program and compiles nothing, so all of your plan's memory goes to the bot. Build on your computer, or build in a GitHub Actions workflow, attach the program to a release and put the file's link in Download link (optional).
Does a teloxide bot use less memory than a Python one?
Compare the numbers: in the same test python-telegram-bot 22.8 used 44 MB and aiogram 3.31.0 used 173 MB, against 13 MB for teloxide. All three fit the 0.5 GB STARTER plan with room for your own code, so pick the language you want to write in.
Ready to run your teloxide bot around the clock? See the plans on Telegram bot hosting.



