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
On this page
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:
| System | Node.js in the packages |
|---|---|
| Debian 12 | 18 |
| Debian 13 | 20 |
| Ubuntu 22.04 | 12 (too old, use NodeSource) |
| Ubuntu 24.04 | 18 |
| AlmaLinux/Rocky 9 | several versions as modules |
Debian and Ubuntu:
apt install -y nodejs npmOn AlmaLinux and Rocky Linux, dnf module list nodejs shows the available versions. Install one of them like this, here 22:
dnf module install -y nodejs:22/commonFrom 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:
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 nodejsNodeSource already includes npm. If the distribution's nodejs is installed, remove it first with apt remove nodejs npm.
Check
node -v
npm -v2. User and folder for the bot
The bot runs as its own user, not as root:
useradd --system --create-home --home-dir /opt/bot --shell /usr/sbin/nologin botPut your code in /opt/bot/app, for example via Git or SFTP, and install the dependencies as bot:
sudo -u bot git clone https://example.com/your/bot.git /opt/bot/app
cd /opt/bot/app
sudo -u bot npm ci --omit=devnpm 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.
install -d -m 0750 /etc/bot
nano /etc/bot/bot.envDISCORD_TOKEN=your-token-from-the-discord-developer-portal
NODE_ENV=productionchmod 600 /etc/bot/bot.envIn your code, read the token with process.env.DISCORD_TOKEN, for example with discord.js:
client.login(process.env.DISCORD_TOKEN);4. systemd service
nano /etc/systemd/system/bot.service[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.targetReplace 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.
systemctl daemon-reload
systemctl enable --now botCheck that it runs
systemctl status botactive (running), and the bot shows as online in your Discord server. The logs:
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
cd /opt/bot/app
sudo -u bot git pull
sudo -u bot npm ci --omit=dev
systemctl restart botNew 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.
npm install -g pm2As your user (not root), in the bot's folder:
pm2 start index.js --name bot
pm2 save
pm2 startup systemdpm2 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
| Problem | Cause and fix |
|---|---|
status=203/EXEC in the status | The 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 intents | In the Developer Portal, under Bot, enable the Privileged Gateway Intents you need. |
SyntaxError or Unsupported engine | Node.js too old. Install a newer LTS version. |
EACCES when writing | The bot may only write in /opt/bot. Give it the files there with chown -R bot:bot /opt/bot. |
| Bot keeps restarting | journalctl -u bot -n 100 shows the error before each restart. |
What next
- Secure your server.
- A database or web UI in containers: Docker.
- Help: ticket.