TutorialsAugust 4, 20264 min read

How to Host a Node.js (discord.js) Bot on a VPS

Deploy a discord.js bot on a VPS from scratch: install Node.js, structure the project, register slash commands, secure the token, and run it with PM2.

NBy Nxeon

discord.js is the most popular way to build Discord bots, and a VPS is the right place to run one in production. This guide takes a discord.js bot from a fresh server to a 24/7 deployment with PM2. For the language-agnostic overview, see how to host a Discord bot on a VPS 24/7.

Step 1: Install Node.js

Use the current LTS. On Ubuntu/Debian:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs git
node -v && npm -v

Step 2: Set up the project

If you're starting fresh:

mkdir my-bot && cd my-bot
npm init -y
npm install discord.js dotenv

Or clone your repo and npm install. Store your token in .env:

echo "DISCORD_TOKEN=your-bot-token-here" > .env
echo "CLIENT_ID=your-application-id" >> .env
echo "node_modules/\n.env" > .gitignore
chmod 600 .env
The Nxeon game-server control panel — live console, player slots, and TPS
The Nxeon game-server control panel — live console, player slots, and TPS

Step 3: A minimal discord.js bot

index.js using the modern gateway intents and a slash command handler:

require("dotenv").config();
const { Client, GatewayIntentBits, Events } = require("discord.js");

const client = new Client({
  intents: [GatewayIntentBits.Guilds]
});

client.once(Events.ClientReady, (c) => {
  console.log(`Logged in as ${c.user.tag}`);
});

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;
  if (interaction.commandName === "ping") {
    await interaction.reply("Pong!");
  }
});

client.login(process.env.DISCORD_TOKEN);

Step 4: Register slash commands

Slash commands must be registered with Discord's API. A deploy-commands.js script:

require("dotenv").config();
const { REST, Routes } = require("discord.js");

const commands = [
  { name: "ping", description: "Replies with Pong!" }
];

const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN);

(async () => {
  await rest.put(
    Routes.applicationCommands(process.env.CLIENT_ID),
    { body: commands }
  );
  console.log("Slash commands registered.");
})();

Run it once after any command change:

node deploy-commands.js

Step 5: Run it 24/7 with PM2

sudo npm install -g pm2
pm2 start index.js --name discord-bot
pm2 save
pm2 startup   # run the sudo command it prints

The full PM2 workflow — logs, memory limits, auto-restart — is covered in how to keep a Discord bot online with PM2.

Step 6: Deploy updates

cd ~/my-bot
git pull
npm install
node deploy-commands.js   # only if commands changed
pm2 restart discord-bot

Production tips

  • Pin Node's version and keep discord.js updated for gateway compatibility.
  • Enable only the intents you use — request privileged intents (like Message Content) in the Developer Portal only if needed.
  • Never commit .env — the .gitignore above prevents leaking your token.
  • Secure the server with SSH keys and a firewall — securing your first Linux VPS: 10 steps.

Prefer containers? See how to deploy a Discord bot with Docker. Node.js also powers a lot more than bots — our Node.js hosting page covers running full apps.

Structuring a bigger bot

A one-file bot is fine to start, but growing bots benefit from a command/event folder structure:

my-bot/
  index.js
  deploy-commands.js
  commands/
    ping.js
    userinfo.js
  events/
    ready.js
    interactionCreate.js

index.js loops over commands/ and events/, registering each. This keeps files small and makes adding a command a matter of dropping in one file.

Sharding for large bots

A single process can serve thousands of servers, but once Discord requires sharding (around 2,500 guilds) you'll use discord.js's ShardingManager:

const { ShardingManager } = require("discord.js");
const manager = new ShardingManager("./index.js", { token: process.env.DISCORD_TOKEN });
manager.spawn();

Run the manager under PM2 instead of the bot directly. Most bots never need this — don't add it prematurely.

Troubleshooting

  • "Used disallowed intents": you requested a privileged intent (Message Content, Presence, Members) without enabling it in the Developer Portal.
  • Commands duplicated: you registered both globally and per-guild — pick one during development.
  • Bot offline after deploy: check pm2 logs; a syntax error or bad token is the usual cause. Node also runs dashboards and APIs happily on the same box — see how to deploy a Node.js app on a VPS.

FAQ

What Node.js version should I use for discord.js?

Use the current LTS (Node 18+ for recent discord.js v14). Older Node versions may fail to start the library.

Why don't my slash commands appear?

You must register them via the REST API (deploy-commands.js). Global commands can take up to an hour to propagate; guild-scoped commands appear instantly.

How do I keep the bot online after I disconnect?

Run it under PM2 (or systemd). Both detach it from your SSH session and restart it on crash and reboot.

Do I need the Message Content intent?

Only if your bot reads message text. Slash-command bots usually don't. Request privileged intents in the Developer Portal only when required.

Ship your discord.js bot on Node.js hosting from Nxeon — NVMe VPS with Node pre-installable via one-click images, full root access, and free migration help.

#discord bot#discord.js#nodejs#pm2#vps#seobatch

Deploy your first server in under a minute

Creating an account is free and takes no card details. You pay when you deploy — choose a billing term and pay from your wallet or by card at checkout.