D Darkless Help
Open the portal

Help Running your server Docker and apps

Run a Node.js app or Discord bot

Install a current Node.js, copy your app up, and run it under systemd so it starts at boot and comes back after a crash.

3 min Updated 7 Nov 2026 Ubuntu, Debian, AlmaLinux

This puts anything you'd start with node index.js on your server, and keeps it running through crashes and reboots with systemd. The example is a Discord bot in a folder called bot.

Install Node.js#

Your system's Node.js is usually behind. Install the current long-term version from NodeSource:

Ubuntu / Debian

curl -fsSL https://deb.nodesource.com/setup_24.x | sudo bash -
sudo apt install -y nodejs

AlmaLinux / Rocky

curl -fsSL https://rpm.nodesource.com/setup_24.x | sudo bash -
sudo dnf install -y nodejs

Check it, and note where node lives, which the service file needs:

node --version && command -v node

Copy your app up#

Make a user for the bot, so it doesn't run as root, and a folder for it:

sudo useradd --system --home /opt/bot --shell /usr/sbin/nologin bot
sudo mkdir -p /opt/bot && sudo chown alex:alex /opt/bot

From your own computer, send the files, leaving out node_modules and .env:

rsync -avz --exclude node_modules --exclude .env ./bot/ [email protected]:/opt/bot/

Or clone it on the server: git clone https://git.example.com/alex/bot.git /opt/bot. Then, on the server, hand the folder to the bot's user and install its packages:

sudo chown -R bot:bot /opt/bot
cd /opt/bot && sudo -u bot npm ci --omit=dev

With no package-lock.json, use npm install --omit=dev.

Keep the token secret#

Put tokens and passwords in a file only root can read:

sudo tee /etc/bot.env <<'EOF'
DISCORD_TOKEN=replace-me
NODE_ENV=production
EOF
sudo chmod 600 /etc/bot.env

The app reads them as process.env.DISCORD_TOKEN, the same as from a .env file.

Run it as a service#

sudo tee /etc/systemd/system/bot.service <<'EOF'
[Unit]
Description=Discord bot
After=network-online.target
Wants=network-online.target

[Service]
User=bot
Group=bot
WorkingDirectory=/opt/bot
EnvironmentFile=/etc/bot.env
ExecStart=/usr/bin/node index.js
Restart=on-failure
RestartSec=5
NoNewPrivileges=true

[Install]
WantedBy=multi-user.target
EOF

Use the path command -v node printed, and your app's start file, in ExecStart. Each line is explained in Run something on boot with systemd.

Start it, now and at every boot:

sudo systemctl daemon-reload && sudo systemctl enable --now bot

Watch its output to check it logged in:

sudo journalctl -u bot -f

Ship a new version#

Copy the new files up as before, then, on the server:

sudo chown -R bot:bot /opt/bot && cd /opt/bot && sudo -u bot npm ci --omit=dev && sudo systemctl restart bot

Firewalls and ports#

A Discord bot only connects out, so it needs no ports opened. An app people visit should listen on 127.0.0.1 with Caddy in front: see Host a website with HTTPS using Caddy.

Python, and other languages#

Any language works the same way; only ExecStart changes. A Python bot in a virtual environment:

ExecStart=/opt/bot/venv/bin/python bot.py

Still stuck? Open a ticket and say which guide you followed.