Tutorial

Run a bot 24/7 on a Vultr VPS with systemd (Ubuntu 24.04)

A VPS gives you a whole Linux server for a fixed monthly price. In exchange, security updates and backups are your job.

Tutorial3 min readUpdated

Your laptopssh -i keySSH :22Vultr VPS (Ubuntu 24.04)ufwallow 22 only (polling)systemd: mybot.serviceuser bot, Restart=on-failureHTTPSTelegramBot API1no inbound port needed for long polling2journalctl -u mybot -f to read logs
Figure 1. The bot runs as an unprivileged systemd service; the firewall only allows SSH.
  1. Create the instance

    In Vultr deploy a Cloud Compute instance with Ubuntu 24.04 LTS, the region closest to your users (Vultr lists Mexico City, several US cities, São Paulo, Europe and Asia), and your SSH public key. On 2026-10-19 the pricing page listed a 1 vCPU / 1 GB Regular Performance plan at $5.00/month, enough for most small bots. A $2.50 plan exists but is IPv6-only; use it only if every API your bot calls supports IPv6.

  2. First login: user, SSH and firewall

    ssh root@YOUR_SERVER_IP
    adduser --disabled-password --gecos "" admin
    usermod -aG sudo admin
    mkdir -p /home/admin/.ssh && cp ~/.ssh/authorized_keys /home/admin/.ssh/
    chown -R admin:admin /home/admin/.ssh
    passwd admin   # needed for sudo
    # disable password and root login over SSH
    printf 'PasswordAuthentication no\nPermitRootLogin no\n' > /etc/ssh/sshd_config.d/10-hardening.conf
    systemctl reload ssh
    ufw allow OpenSSH && ufw --force enable
    apt update && apt -y upgrade && apt -y install unattended-upgrades unzip

    Open a second terminal and confirm ssh admin@YOUR_SERVER_IP works before closing the root session.

  3. Install Node.js 22

    curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
    less nodesource_setup.sh   # read before running
    sudo bash nodesource_setup.sh
    sudo apt -y install nodejs
    node --version
  4. Install the bot as an unprivileged user

    sudo useradd --system --home /opt/mybot --shell /usr/sbin/nologin bot
    sudo mkdir -p /opt/mybot/data
    cd /tmp && curl -LO https://botnetwork.tech/downloads/telegram-grammy.zip
    sudo unzip -o telegram-grammy.zip -d /opt/mybot
    cd /opt/mybot && sudo npm install --omit=dev
    sudo install -m 600 /dev/null /opt/mybot/.env
    sudo nano /opt/mybot/.env      # BOT_TOKEN=...  MODE=polling
    sudo chown -R bot:bot /opt/mybot

    The token file is readable only by the bot user.

  5. Create the systemd service

    /etc/systemd/system/mybot.service

    [Unit]
    Description=mybot chat bot
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=bot
    WorkingDirectory=/opt/mybot
    EnvironmentFile=/opt/mybot/.env
    ExecStart=/usr/bin/node src/index.js
    Restart=on-failure
    RestartSec=5
    MemoryMax=256M
    NoNewPrivileges=true
    ProtectSystem=strict
    ProtectHome=true
    PrivateTmp=true
    ReadWritePaths=/opt/mybot/data
    
    [Install]
    WantedBy=multi-user.target
    sudo systemctl daemon-reload
    sudo systemctl enable --now mybot
    sudo systemctl status mybot
    bot@vps$ sudo systemctl status mybot● mybot.service - mybot chat bot Active: active (running) since Mon 2026-10-19 20:12:03 UTC Main PID: 2141 (node) Memory: 58.3M (max: 256.0M)$ journalctl -u mybot -n 2mybot[2141]: @mi_primer_demo_bot running with long polling1running2MemoryMax works
    Figure 2. A healthy service (illustration).

    Restart=on-failure brings the bot back after a crash; enable starts it on boot. The Protect* lines limit what a compromised bot could touch. Generate a unit for your own setup with the hosting config generator.

  6. Logs, updates and backups

    journalctl -u mybot -f                 # follow logs
    sudo systemctl restart mybot           # after changing code or .env
    sudo unattended-upgrade --dry-run       # security updates run daily

    Vultr's pricing page (verified 2026-10-19) says automatic backups add 20% to the instance price. Your bot's data folder can also be copied off the server with a cron job. vultr.com/pricing

Troubleshooting

  • status=203/EXEC: the ExecStart path is wrong. Run which node and fix it.
  • EACCES / read-only file system: ProtectSystem=strict blocks writes outside ReadWritePaths. Put files in /opt/mybot/data.
  • Webhook mode: you then need a domain, TLS and a reverse proxy (Caddy or nginx) on 443, and ufw allow 443. Long polling avoids all of that.