🤖
Hosting

Running a Python Bot 24/7 with systemd and PM2

09.11.2026
← All articles

Most people deploy their first Python bot by logging in over SSH, typing python3 bot.py and watching it work. Then the terminal closes or the SSH session drops, and the bot dies with it. nohup or screen come next, but after a reboot or an unhandled exception nothing brings the bot back. To run a bot around the clock, it has to become a service managed by the operating system.

Why regular hosting is the wrong place

Shared hosting is built for PHP sites: a request arrives, a script runs and exits. Long-running processes in that environment are usually stopped by resource limits or the system itself, and cron-based keepalive tricks are unreliable. A bot that must run 24/7 belongs on a VPS with root access, where you create a systemd service and the bot starts together with the system.

Preparing a dedicated user and a virtualenv

Running a bot as root is a bad security habit. Create a system user for the bot, place the code in its home directory and install libraries into a virtual environment so they never clash with other projects.

sudo adduser --system --group --home /opt/mybot mybot
sudo -u mybot python3 -m venv /opt/mybot/venv
sudo -u mybot /opt/mybot/venv/bin/pip install -r /opt/mybot/requirements.txt

Keep the bot token out of the code. Store it in a .env file readable only by the bot user, which also prevents it from leaking to GitHub by accident.

sudo nano /opt/mybot/.env
# BOT_TOKEN=123456:ABC...
sudo chown mybot:mybot /opt/mybot/.env
sudo chmod 600 /opt/mybot/.env

Creating the systemd service

systemd starts the process, restarts it when it crashes, enables it at boot and captures all output in the journal. One unit file is enough.

sudo nano /etc/systemd/system/mybot.service
[Unit]
Description=Telegram bot (mybot)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=mybot
WorkingDirectory=/opt/mybot
EnvironmentFile=/opt/mybot/.env
ExecStart=/opt/mybot/venv/bin/python -u bot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Restart=always brings the bot back five seconds after it stops for any reason, and -u disables Python output buffering so log lines appear immediately. Then reload systemd and enable the service.

sudo systemctl daemon-reload
sudo systemctl enable --now mybot
systemctl status mybot

Reading the logs

No separate log file is needed because the journal collects everything. These commands show recent lines, follow new ones live, and restart the service after a code update.

journalctl -u mybot -n 100
journalctl -u mybot -f
sudo systemctl restart mybot

The PM2 alternative

PM2 was built for Node.js but manages Python scripts just as well. If the server already runs Node.js apps under PM2, keeping everything in one list is convenient. It requires Node.js and npm.

sudo npm install -g pm2
cd /opt/mybot
pm2 start bot.py --name mybot --interpreter /opt/mybot/venv/bin/python
pm2 save
pm2 startup

pm2 startup prints a sudo command that you must copy and run, otherwise PM2 will not start after a reboot. pm2 save stores the current process list. Use pm2 logs mybot and pm2 restart mybot afterwards.

systemd or PM2

CriterionsystemdPM2
Extra installNone, built inNode.js and npm
Logsjournalctlpm2 logs, ~/.pm2/logs
Overviewsystemctl list-unitspm2 list, pm2 monit
Best forPure Python serversMixed Node.js servers

Example: a Tashkent flower shop bot

A flower shop in Tashkent takes orders through a Telegram bot that writes them into the database behind misol.uz. The bot used to run on the developer's laptop, and orders were lost every night when the lid closed. Moved to a small VPS under the systemd service above, it now starts with the server and recovers from network errors within five seconds. When something goes wrong, the owner runs journalctl -u mybot and sends the exact error to the developer.

Common mistakes

If your bot needs a server, you can order a VPS in the sayt.uz cabinet and apply this setup in minutes.

Related articles

🧠 Out of Memory (OOM): Why PHP and Python Processes Get Killed 🧠 "Allowed memory size exhausted": fixing the PHP memory error properly 🚀 Full-page caching: when it helps and when it gets in the way ⚡ Deploying FastAPI to a Server: Uvicorn, systemd and Nginx
🌐 Language
🇺🇿 O'zbek 🇺🇿 Ўзбек 🇷🇺 Русский 🇬🇧 English ✓