Skip to content
CitexNode
All guides

Node.js and Discord bots

Install Node.js LTS and run a Discord bot as a systemd service with its token in an env file.

5 min read

Applies to Debian 12/13, Ubuntu 22.04/24.04, AlmaLinux 9 and Rocky Linux 9. In the end your bot runs as a service, starts at boot, comes back after crashes and writes its logs to the journal.

1. Install Node.js

Use an LTS version of Node.js. Which one is current is listed on https://nodejs.org. Libraries such as discord.js state their minimum version in their documentation.

From the distribution's packages

Easiest, but often older:

SystemNode.js in the packages
Debian 1218
Debian 1320
Ubuntu 22.0412 (too old, use NodeSource)
Ubuntu 24.0418
AlmaLinux/Rocky 9several versions as modules

Debian and Ubuntu:

bash
apt install -y nodejs npm

On AlmaLinux and Rocky Linux, dnf module list nodejs shows the available versions. Install one of them like this, here 22:

bash
dnf module install -y nodejs:22/common

From the official NodeSource repository (Debian and Ubuntu)

For a current LTS version on Debian and Ubuntu. Set NODE_MAJOR to the major version you want:

bash
apt install -y ca-certificates curl gpg
install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=26
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" > /etc/apt/sources.list.d/nodesource.list
apt update
apt install -y nodejs

NodeSource already includes npm. If the distribution's nodejs is installed, remove it first with apt remove nodejs npm.

Check

bash
node -v
npm -v

2. User and folder for the bot

The bot runs as its own user, not as root:

bash
useradd --system --create-home --home-dir /opt/bot --shell /usr/sbin/nologin bot

Put your code in /opt/bot/app, for example via Git or SFTP, and install the dependencies as bot:

bash
sudo -u bot git clone https://example.com/your/bot.git /opt/bot/app
cd /opt/bot/app
sudo -u bot npm ci --omit=dev

npm ci needs a package-lock.json. Without one, use npm install --omit=dev.

3. Put the token in an env file

The bot token doesn't belong in your code or your Git repo. Put it in a file only root can read; systemd hands it to the bot at start.

bash
install -d -m 0750 /etc/bot
nano /etc/bot/bot.env
ini
DISCORD_TOKEN=your-token-from-the-discord-developer-portal
NODE_ENV=production
bash
chmod 600 /etc/bot/bot.env

In your code, read the token with process.env.DISCORD_TOKEN, for example with discord.js:

javascript
client.login(process.env.DISCORD_TOKEN);

4. systemd service

bash
nano /etc/systemd/system/bot.service
ini
[Unit]
Description=Discord bot
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=bot
WorkingDirectory=/opt/bot/app
EnvironmentFile=/etc/bot/bot.env
ExecStart=/usr/bin/node index.js
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
ProtectSystem=full
ProtectHome=true

[Install]
WantedBy=multi-user.target

Replace index.js with your bot's entry file. To start with npm start: ExecStart=/usr/bin/npm start. command -v node shows where node lives.

bash
systemctl daemon-reload
systemctl enable --now bot

Check that it runs

bash
systemctl status bot

active (running), and the bot shows as online in your Discord server. The logs:

bash
journalctl -u bot -f

-f follows new lines live; Ctrl+C stops the display (not the bot). The last 100 lines: journalctl -u bot -n 100.

Update the bot

bash
cd /opt/bot/app
sudo -u bot git pull
sudo -u bot npm ci --omit=dev
systemctl restart bot

New token? Edit /etc/bot/bot.env and run systemctl restart bot.

Alternative: pm2

pm2 is a process manager for Node.js. It's handy if you run many Node apps; for a single bot, systemd is enough.

bash
npm install -g pm2

As your user (not root), in the bot's folder:

bash
pm2 start index.js --name bot
pm2 save
pm2 startup systemd

pm2 startup prints a line beginning with sudo env PATH=…. Run it once so pm2 starts at boot. Logs: pm2 logs bot, status: pm2 status.

With pm2, pass the token via an ecosystem.config.js or via node --env-file=.env index.js. The .env then goes into .gitignore. Use either systemd or pm2 for a given bot, not both.

Common errors

ProblemCause and fix
status=203/EXEC in the statusThe path in ExecStart is wrong. command -v node shows the right one.
Error [TokenInvalid]Token wrong or reset. Generate a new one in the Discord Developer Portal, put it in bot.env, restart.
Used disallowed intentsIn the Developer Portal, under Bot, enable the Privileged Gateway Intents you need.
SyntaxError or Unsupported engineNode.js too old. Install a newer LTS version.
EACCES when writingThe bot may only write in /opt/bot. Give it the files there with chown -R bot:bot /opt/bot.
Bot keeps restartingjournalctl -u bot -n 100 shows the error before each restart.

What next