How to Host a Discord Bot on a VPS 24/7 (systemd Guide, 2026)

Run a Discord bot 24/7 on a VPS with a sandboxed systemd unit, a root-only token file and a restart policy that protects your token. Python and Node.js code, sizing and real monthly cost.

HourlyVPS blog cover reading Discord bot on a VPS, 24/7, for a guide to running a discord.py or discord.js bot under systemd

To host a Discord bot on a VPS 24/7, run it as a systemd service under its own Linux user, keep the token in a root-only environment file, and set Restart=always, so the bot starts at boot, comes back after crashes and logs to journalctl. A small slash-command bot fits on a 1 GB plan, and although it runs every hour of the month, hourly billing never charges more than the plan’s monthly price in a billing period.

The steps use discord.py 2.7.1 and discord.js 14.27.0 on Ubuntu 24.04 LTS (current on October 3, 2026) and cover Discord’s June 2026 privileged-intent change, a restart policy that protects your token, sizing, cost and a token-leak checklist.

Key takeaways

  • A Gateway bot needs a process that never stops; a VPS with a systemd unit (own user, Restart=always) gives you start at boot, crash recovery and journalctl logs without screen or tmux.
  • Since June 10, 2026, Discord's privileged-intent review starts at 10,000 unique users instead of 100 servers; 100 servers now triggers only app verification, and slash commands need no privileged intents at all.
  • Keep the token in a root-owned 0600 EnvironmentFile that systemd reads before it drops to the bot user, or use LoadCredential= for a stricter setup.
  • Add restart backoff: Discord resets a bot's token after 1,000 IDENTIFY calls in 24 hours, and a tight crash loop can burn through that.
  • A small slash-command bot fits a 1 GB plan, and although it runs every hour, its hourly charges stop once they reach the plan's monthly price in a billing period.

Do you need a VPS to host a Discord bot?

It depends on how your bot receives events. Discord delivers slash commands and other interactions in one of two ways, and each app must pick one:

  • Gateway: the bot keeps a WebSocket connection open to Discord and receives interactions plus events such as messages, member joins, reactions and voice updates as they happen. This needs a process that never stops.
  • Interactions Endpoint URL (HTTP): Discord sends each slash command, button click or menu choice to your HTTPS endpoint, and your code must send the first response within 3 seconds. Nothing has to stay connected between requests.

That choice decides where the bot can live:

Where it runsKeeps a Gateway connection?Good fit forWatch out for
Your own PC or laptopOnly while it is on and awakeDevelopmentSleep, reboots, home connection drops
Raspberry Pi or home serverYes, while power and internet stay upA hobby botPower and ISP outages; you maintain the hardware
Free app-hosting tiersDepends on the platform’s idle rulesTrying an ideaIdle and usage limits vary and change
Serverless functions (for example Cloudflare Workers)No: code runs per requestSlash-command-only bots on an Interactions Endpoint URLNo message, member, reaction or voice events
VPSYes, 24/7Any Gateway bot, voice, scheduled jobs, a local databaseYou handle updates and security (covered below)
Where a Discord bot can run. Gateway and Interactions behavior per Discord’s developer documentation, checked October 3, 2026.

If your bot only answers slash commands, Discord’s own tutorial shows an HTTP-only app on Cloudflare Workers, and you may not need a server at all. Self-hosting such an endpoint needs HTTPS, for example a Caddy reverse proxy on the VPS. Any bot that reacts to messages, members or voice needs a process that stays connected; the rest of this guide sets that up.

Step 1: Create the bot in the Discord Developer Portal

  1. Open the Discord Developer Portal, choose New Application and give it a name.
  2. On the Bot page, choose Reset Token and copy the token. Discord shows it only once, so store it in a password manager. Anyone who has it can act as your bot.
  3. On the same page, leave the Privileged Gateway Intents switched off unless your code needs them (see the next section).
  4. On the Installation page, under Guild Install, add the applications.commands and bot scopes and only the permissions the bot needs, such as Send Messages. Avoid Administrator.
  5. Open the install link and add the bot to a test server.

Privileged intents after Discord’s June 2026 change

Discord has three privileged intents: Presence (GUILD_PRESENCES), Server Members (GUILD_MEMBERS) and Message Content (MESSAGE_CONTENT). Without Message Content, the content fields of messages arrive empty, except for messages your app sends, DMs with the app, messages that mention it, and message context-menu commands.

On June 10, 2026, Discord replaced the old 100-server rule with a user count. Apps with fewer than 10,000 unique users, counted across every server the app is installed in, can switch privileged intents on in the portal. The portal does not display that count. When an app passes 10,000, Discord notifies the owner by email or system DM and shows an alert on the app’s portal page, and the owner then has 90 days to apply for review.

The bot keeps its intents and can keep joining servers while the review runs, and approved apps reapply once a year (Discord’s announcement). App verification is now a separate review: it is still required before an app can grow past 100 servers, and the owner of the app’s team must verify their identity through Stripe. Older guides that tie privileged intents to 100 servers describe the old combined review.

Tip: Slash commands need no privileged intents. Discord’s documentation names prefix commands (such as !help) as the most common reason developers request Message Content and recommends slash commands instead. Because the count is unique users with access, a single server with more than 10,000 members can put your bot over the threshold on its own.

Step 2: Prepare the VPS and install Python or Node.js

Deploy Ubuntu 24.04 LTS, log in with an SSH key as a sudo user (see how to connect to a VPS over SSH), and work through the new Linux VPS security checklist. Then bring the system up to date:

sudo apt update && sudo apt upgrade -y

A Gateway bot only makes outbound HTTPS and WebSocket connections to Discord. It listens on no port, so the firewall needs to allow SSH and nothing else:

sudo ufw allow OpenSSH
sudo ufw enable

Create a system user that owns the bot and cannot log in. Its home directory, /opt/discord-bot, holds the code and the dependencies:

sudo useradd --system --user-group --create-home --home-dir /opt/discord-bot --shell /usr/sbin/nologin discordbot

Then follow option A for Python or option B for Node.js.

Option A: Python with discord.py

Ubuntu 24.04 ships Python 3.12, and discord.py 2.7.1 (released March 3, 2026) needs Python 3.8 or newer. Ubuntu marks its system Python as externally managed (PEP 668), so pip refuses to install into it. Use a virtual environment owned by the bot user:

sudo apt install -y python3-venv git
sudo -H -u discordbot python3 -m venv /opt/discord-bot/venv

Create the app folder, pin the library version and install it:

sudo -H -u discordbot mkdir -p /opt/discord-bot/app
echo "discord.py==2.7.1" | sudo -H -u discordbot tee /opt/discord-bot/app/requirements.txt
sudo -H -u discordbot /opt/discord-bot/venv/bin/pip install -r /opt/discord-bot/app/requirements.txt

Open /opt/discord-bot/app/bot.py with sudo -H -u discordbot nano /opt/discord-bot/app/bot.py and paste this minimal slash-command bot:

import logging
import os

import discord
from discord import app_commands

log = logging.getLogger("bot")


class SlashBot(discord.Client):
    def __init__(self) -> None:
        # Only the non-privileged Guilds intent. Slash commands need nothing more.
        super().__init__(intents=discord.Intents(guilds=True))
        self.tree = app_commands.CommandTree(self)

    async def setup_hook(self) -> None:
        # One bulk-overwrite request that publishes the commands below.
        await self.tree.sync()


bot = SlashBot()


@bot.tree.command(description="Check that the bot is alive")
async def ping(interaction: discord.Interaction) -> None:
    await interaction.response.send_message(
        f"Pong! Gateway latency: {bot.latency * 1000:.0f} ms"
    )


@bot.event
async def on_ready() -> None:
    log.info("Logged in as %s (ID %s)", bot.user, bot.user.id)


bot.run(os.environ["DISCORD_TOKEN"], root_logger=True)

Intents(guilds=True) asks only for the non-privileged Guilds intent, like the Node.js sample below; Discord delivers interactions to a bot whatever intents it requests. setup_hook publishes the command list once per start with a single bulk-overwrite request. Only commands that did not exist before count toward Discord’s limit of 200 command creates per day per server, so republishing an unchanged list is harmless; once the list changes often, move the sync into your deploy step. root_logger=True sends both the library’s and your own log lines to stderr, which systemd passes to the journal.

Option B: Node.js with discord.js

discord.js 14.27.0 accepts Node.js 18 or newer, but Ubuntu 24.04’s own nodejs package is 18.19.1, and Node.js 18 reached upstream end of life on April 30, 2025. Install Node.js 24 LTS (supported until April 30, 2028) from the NodeSource repository instead. It also covers the next major version: the discord.js v15 development README requires Node.js 24.17.0 or newer. Download the setup script and read it before you run it:

curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs git
node -v

Install discord.js as the bot user, pinned to an exact version:

sudo -H -u discordbot mkdir -p /opt/discord-bot/app
sudo -H -u discordbot npm --prefix /opt/discord-bot/app install --save-exact [email protected]

Create /opt/discord-bot/app/index.mjs with sudo -H -u discordbot nano /opt/discord-bot/app/index.mjs. The .mjs extension lets Node.js use import without editing package.json:

import { Client, Events, GatewayIntentBits } from 'discord.js';

// Guilds is not a privileged intent. Slash commands need nothing more.
const client = new Client({ intents: [GatewayIntentBits.Guilds] });

const commands = [
  { name: 'ping', description: 'Check that the bot is alive' },
];

client.once(Events.ClientReady, async (readyClient) => {
  // One bulk-overwrite request that publishes the commands above.
  await readyClient.application.commands.set(commands);
  console.log(`Logged in as ${readyClient.user.tag}`);
});

client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;
  if (interaction.commandName === 'ping') {
    await interaction.reply(`Pong! Gateway latency: ${client.ws.ping} ms`);
  }
});

client.login(process.env.DISCORD_TOKEN);

GatewayIntentBits.Guilds is not privileged. commands.set() replaces the global command list in one request each time the bot logs in.

Step 3: Keep the token in a root-only environment file

Keep the token out of the code, out of any .env file inside the repository, and out of Environment= lines in the unit, because unit files are normally world-readable and systemd exposes Environment= values over D-Bus. Put it in a file only root can read. systemd reads EnvironmentFile= itself, as root, before it starts the bot as discordbot, so the bot user never needs access to the file on disk.

sudo install -d -m 700 /etc/discord-bot
sudo install -m 600 /dev/null /etc/discord-bot/bot.env
sudoedit /etc/discord-bot/bot.env

Add one line with your token after the equals sign, then save:

DISCORD_TOKEN=paste-the-token-here

sudo ls -l /etc/discord-bot/bot.env should now show -rw------- 1 root root. Typing the token into an editor instead of an echo command also keeps it out of your shell history.

Warning: environment variables are visible to the bot process and to every program it starts. systemd’s own documentation says they are not suitable for secrets and recommends LoadCredential= instead (next section). For a single-purpose bot user, a root-owned 0600 environment file is still a large step up from a .env file in the project folder.

Stricter: pass the token with LoadCredential=

systemd 247 and newer (Ubuntu 24.04 ships 255, Debian 12 ships 252) can hand the token to the service as a read-only file that only the service user can read. Save the bare token, without the DISCORD_TOKEN= prefix, in /etc/discord-bot/token with mode 600 and replace the EnvironmentFile= line in the unit with:

LoadCredential=discord_token:/etc/discord-bot/token

Then read it in Python like this and pass token to bot.run(). In Node.js, read the same path with fs.readFileSync, call .trim() and pass the result to client.login():

import os
from pathlib import Path

token = (Path(os.environ["CREDENTIALS_DIRECTORY"]) / "discord_token").read_text().strip()

Step 4: Host a Discord bot on a VPS 24/7 with systemd

Create the unit file with sudo nano /etc/systemd/system/discord-bot.service and paste this Python version:

[Unit]
Description=Discord bot (discord.py)
Wants=network-online.target
After=network-online.target

[Service]
Type=exec
User=discordbot
Group=discordbot
WorkingDirectory=/opt/discord-bot/app
EnvironmentFile=/etc/discord-bot/bot.env
Environment=PYTHONUNBUFFERED=1
ExecStart=/opt/discord-bot/venv/bin/python bot.py
Restart=always
RestartSec=10s
RestartSteps=5
RestartMaxDelaySec=5min
MemoryHigh=400M
MemoryMax=512M
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

For discord.js, change these three lines and keep the rest:

Description=Discord bot (discord.js)
Environment=NODE_ENV=production
ExecStart=/usr/bin/node index.mjs

What each part does:

SettingWhy it is there
Type=execsystemctl start fails loudly when the program cannot run (wrong path, missing user). The systemd manual recommends it for long-running services.
User=, Group=No root. A bug or a compromised dependency gets the bot user’s rights only.
EnvironmentFile=Loads DISCORD_TOKEN from the root-only file; a missing file stops the start.
Environment=PYTHONUNBUFFERED=1Without it, Python block-buffers stdout under systemd, so print() lines reach the journal late, or never if the process is killed.
Restart=alwaysRestarts after crashes, kills and clean exits alike. The systemd manual recommends on-failure for long-running services; a bot should never exit on its own, so always is the simpler rule.
RestartSec=10s, RestartSteps=5, RestartMaxDelaySec=5minExponential backoff: waits of about 10 s, 20 s, 39 s, 77 s and 152 s, then 5 minutes per attempt.
MemoryHigh=400M, MemoryMax=512MGuard rails for a 1 GB server: above 400 MiB the kernel slows the bot and reclaims its memory, at 512 MiB the OOM killer stops it and systemd restarts it, and SSH stays usable. Tune after you measure.
NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmpRead-only filesystem (the bot cannot modify its own code), no access to /home, a private /tmp, no setuid escalation. For an SQLite file, add StateDirectory=discord-bot and write to /var/lib/discord-bot.
Wants= and After=network-online.targetStarts the bot after the network is up at boot.
WantedBy=multi-user.targetLets systemctl enable start the bot at every boot.
The discord-bot.service unit, line by line. Directive behavior per the systemd.service and systemd.exec manuals.

Why the backoff matters on Discord: Discord allows 1,000 IDENTIFY calls (new Gateway sessions) per bot in 24 hours. Past that, Discord ends all sessions, resets the bot token and emails the owner. Every process restart opens a new session. With a flat 10-second delay, a bot that crashes right after connecting could restart up to 8,640 times a day (86,400 s ÷ 10 s); with the 5-minute ceiling above, the worst case is 288 a day.

Debian 12: it ships systemd 252, and RestartSteps= and RestartMaxDelaySec= arrived in systemd 254, so Debian 12 ignores both lines with a warning. Use a single RestartSec=2min there instead, which caps a crash loop at 720 restarts a day.

Check the file, then enable and start the service:

sudo systemd-analyze verify /etc/systemd/system/discord-bot.service
sudo systemctl daemon-reload
sudo systemctl enable --now discord-bot
sudo systemctl status discord-bot

Look for active (running) and your Logged in as line, then try /ping in your server. Now test the two promises. Kill the process and watch systemd start it again about 10 seconds later, then reboot and confirm the bot came back on its own:

sudo systemctl kill --signal=SIGKILL discord-bot
sudo reboot

Optional: systemd-analyze security discord-bot scores the unit’s sandboxing and lists further settings you can tighten. Our guide to creating a systemd service explains each directive in this unit, the restart backoff and the sandboxing options in more depth.

Why systemd instead of screen, pm2 or Docker?

Many tutorials keep the bot alive in screen, which survives an SSH disconnect and nothing else. Here is how the common options compare:

OptionAfter a crashAfter a rebootLogsFits when
screen or tmuxStays offlineStays offlineTerminal scrollback onlyWatching a quick test run
pm2Restarts itStarts it only after pm2 startup and pm2 save; on Ubuntu, pm2 generates a systemd unit for itselfpm2 logsYou already manage Node.js apps with pm2
systemd (this guide)Restarts it, with backoffStarts it (systemctl enable)journalctlAny language, nothing extra to install
Docker ComposeRestarts it with restart: always or unless-stoppedStarts it when the Docker service startsdocker compose logsYou already run containers
Ways to keep a Discord bot online. Behavior per the systemd manual, pm2 startup documentation and Docker restart-policy documentation, checked October 3, 2026.

For one or two bots, systemd is one layer fewer. If you already run containers, see how to install Docker on a VPS and Docker’s restart policies; for pm2, see its startup script guide.

Read logs with journalctl and fix common errors

Everything the bot writes to stdout and stderr goes to the systemd journal, with timestamps, across restarts. When the journal is stored on disk, its default cap is 10% of the filesystem or 4 GB, whichever is smaller: about 2.5 GB on a 25 GB disk.

GoalCommand
Follow the log livesudo journalctl -u discord-bot -f
Last 100 linessudo journalctl -u discord-bot -n 100 --no-pager
Only the last hoursudo journalctl -u discord-bot --since "1 hour ago"
Only since the last bootsudo journalctl -u discord-bot -b
Find tracebacks and errorssudo journalctl -u discord-bot --no-pager | grep -E "Traceback|Error"
See how much disk the journal usessudo journalctl --disk-usage
journalctl commands for the discord-bot unit.

systemd records plain stderr lines at the info level, so a Python traceback is not tagged as an error and journalctl -p err will not find it. Search the text instead, as in the table.

Troubleshooting matrix

What you seeLikely causeFix
LoginFailure: Improper token has been passed. (discord.py) or TokenInvalid: An invalid token was provided. (discord.js)Wrong token, or the token was resetCopy a fresh token into bot.env with sudoedit, then restart
KeyError: 'DISCORD_TOKEN' (discord.py)The variable never reached the process. In discord.js the same mistake shows up as TokenInvalidCheck the EnvironmentFile= path and the variable name, then run sudo systemctl daemon-reload and restart
ModuleNotFoundError: No module named 'discord' or Cannot find package 'discord.js'ExecStart= uses the system Python instead of the venv, or the dependencies were never installed in the app folderPoint ExecStart= at /opt/discord-bot/venv/bin/python; rerun the pip or npm install as discordbot
PrivilegedIntentsRequired or Used disallowed intentsThe code requests an intent that is off in the portalEnable it in the portal, or remove it from the code
Read-only file system (Errno 30 in Python, EROFS in Node.js)The bot writes a file, and ProtectSystem=strict keeps the disk read-onlyAdd StateDirectory=discord-bot and save data under /var/lib/discord-bot (systemd passes the path as $STATE_DIRECTORY)
status=203/EXECThe program in ExecStart= is missing or not executableCheck /opt/discord-bot/venv/bin/python or /usr/bin/node
status=200/CHDIRWorkingDirectory= is missing or unreadable for the bot userFix the path or its ownership
Failed with result 'oom-kill'The bot hit MemoryMax=Measure, raise the limits, trim caches or move to a larger plan
Scheduled restart job, restart counter is at followed by a numberA crash loopRead the lines just above it for the real error
The slash command never appearsThe sync failed or never ran, or the Discord app has not refreshed its command listCheck the log for sync errors, then reload Discord. The bot scope already includes applications.commands
Discord says the application did not respondThe bot is offline, or took longer than 3 secondsCheck the service; defer the response for slow commands
Common Discord bot errors on a VPS. Error strings from discord.py 2.7.1, discord.js 14.27.0, Node.js and systemd.

How to update and redeploy a Discord bot

Keep the code in Git and let the server pull it. For a private repository, give the server a read-only deploy key rather than your personal credentials. The first deploy clones into an empty app folder (move the sample files away first if you created them):

sudo -H -u discordbot git clone https://github.com/you/your-bot.git /opt/discord-bot/app

After the clone, run the deploy script from the next step once; it installs the dependencies and restarts the service.

Each update is the same four steps: note the running commit, pull, refresh dependencies and restart. Save them as /usr/local/bin/deploy-discord-bot with sudo nano, then make the script executable with sudo chmod 755 /usr/local/bin/deploy-discord-bot:

#!/usr/bin/env bash
set -euo pipefail
APP=/opt/discord-bot/app

echo "Running commit before update:"
sudo -H -u discordbot git -C "$APP" rev-parse --short HEAD
sudo -H -u discordbot git -C "$APP" pull --ff-only
sudo -H -u discordbot /opt/discord-bot/venv/bin/pip install -r "$APP/requirements.txt"
sudo systemctl restart discord-bot
sudo journalctl -u discord-bot -n 20 --no-pager

For Node.js, replace the pip line with sudo -H -u discordbot npm --prefix "$APP" ci --omit=dev, which installs exactly what package-lock.json lists. If an update misbehaves, roll back to the commit the script printed, reinstall that commit’s dependencies (the npm line instead of pip for Node.js) and restart:

sudo -H -u discordbot git -C /opt/discord-bot/app reset --hard <commit>
sudo -H -u discordbot /opt/discord-bot/venv/bin/pip install -r /opt/discord-bot/app/requirements.txt
sudo systemctl restart discord-bot

No Git? Copy the files with scp

If the bot lives only on your computer, copy its folder with scp, part of OpenSSH. Leave venv, node_modules and any .env file out: the server builds its own dependencies and already holds the token. On your computer, with your sudo user and the server’s IP address, run:

scp -r my-bot [email protected]:

Then, on the server, move the files into place, hand them to the bot user, install the dependencies (use the npm line for Node.js) and restart:

sudo cp -r ~/my-bot/. /opt/discord-bot/app/
sudo chown -R discordbot:discordbot /opt/discord-bot/app
sudo -H -u discordbot /opt/discord-bot/venv/bin/pip install -r /opt/discord-bot/app/requirements.txt
sudo systemctl restart discord-bot

Git stays the better habit: every deploy is a reviewable commit, and a rollback is one command.

How much RAM does a Discord bot need?

A small slash-command bot that uses only the Guilds intent fits on a 1 GB VPS. Memory grows with privileged member caches, guild count and audio work. Discord and the libraries publish no official footprint figure, so size from what drives memory, then measure your own bot:

  • The OS: Ubuntu Server 24.04 lists a minimum of 1 GB of RAM and 4 GB of storage for cloud images, and a suggested minimum of 3 GB of RAM and 25 GB of storage. A server that runs one small bot and nothing else sits near the minimum, which is why Q1 is the starting point and the unit caps the bot at 512 MiB.
  • Message cache: discord.py keeps up to 1,000 messages by default (max_messages); discord.js keeps 200 messages per channel by default. Both are adjustable.
  • Member cache: with the Server Members intent, both libraries cache members, so memory scales with the people in your servers. discord.py also chunks guilds at startup by default when that intent is on, which its docs call “incredibly slow for large amounts of guilds”.
  • Sharding: Discord requires it once a bot is in 2,500 guilds. Each shard is its own Gateway connection.
  • CPU: a Gateway bot waits for events most of the time. Sustained CPU comes from audio transcoding, image generation or heavy loops. If the bot wraps an AI model or agent, sizing a VPS for AI agents covers calling a hosted model versus running one on the server.
Bot profileWhat drives resourcesStart on
Slash-command or utility bot, Guilds intent only, a handful of serversOne runtime plus default cachesQuartz Q1: 1 vCPU, 1 GB RAM, 25 GB NVMe
Bot plus a database or a web dashboard, or two or three small botsExtra processesQuartz Q2: 1 vCPU, 2 GB RAM, 50 GB NVMe
Moderation or logging bot with Server Members or Presence intents in large serversMember and presence caches grow with member countQuartz Q4: 2 vCPU, 4 GB RAM, 80 GB NVMe
Music bot with Lavalink (a separate Java 17+ process), many voice streams, or a bot near 2,500 guildsAudio encoding CPU, a second runtime, several shardsQuartz Q8 (4 vCPU, 8 GB) or Chrono C8 (2 dedicated vCPU, 8 GB)
Starting points by reasoning from documented defaults, not benchmarks. Measure, then resize.

To measure, read the Memory: line of sudo systemctl status discord-bot, or watch every unit live with systemd-cgtop. Observe a few days of normal use, including a restart, then set MemoryHigh= and MemoryMax= with headroom above the peak you saw. Running a Telegram bot on the same server? The same user-plus-unit pattern works; see the Telegram bot VPS guide.

Quartz Q1

KVM · 1 Gbps port · Istanbul · initial credit $5

vCPU
1 shared
RAM
1 GB
NVMe
25 GB
Traffic
Istanbul: 2 TB/month
  • Per hour$0.01/hour
  • Per day (24 h)$0.24/day
  • Monthly cap$5.00/month

Deploy Quartz Q1 Plan details for Quartz Q1

Does location matter for a Discord bot?

discord.com and gateway.discord.gg resolve to Cloudflare addresses (checked October 3, 2026), and Discord does not document where its Gateway servers sit behind that edge. So measure: run the bot for an hour on an hourly server, at $0.01 per test, and compare its /ping results, which report the Gateway heartbeat latency (bot.latency, client.ws.ping), with those from where it runs now. Run one copy at a time: stop the bot on one machine before you start it on the other.

If the numbers are close, choose the location for whatever else the bot talks to, such as your database, other APIs or the users of a web dashboard. Istanbul plans include the monthly traffic allowance listed for each plan, prorated for a server that exists for part of a billing period. For the wider trade-offs, read how to choose a VPS location or see our locations: Istanbul today, with New York coming soon.

How much does it cost to host a Discord bot 24/7?

A small bot on Quartz Q1 costs at most $5.00 per billing period. The server is billed by the hour at $0.01/hour, and once its charges in a billing period (one month from your order date) reach the plan’s monthly price, after 500 hours (about 20.8 days), they stop for the rest of that period. A 24/7 bot runs all 720 hours of a 30-day period, so it always reaches that cap: 30 days of uptime come to $5.00.

Quartz Q1 total cost by duration, billed by the hour and capped at the monthly price
DurationHours on the meterCost $0.01/hour · cap $5.00/monthNote
8 hours8$0.08
1 day24$0.24
7 days168$1.68
30 days720$5.00Capped at the monthly price
Charges stop at $5.00 after 500 hours (about 20.8 days) in a billing period; the rest of that period is free.

There is no billing mode to choose up front and none to switch later. The same hourly server works as a throwaway test box, where an evening of experiments, 8 hours, costs $0.08, and as the bot’s permanent home, where the cap makes it a monthly VPS with no contract and no monthly prepayment.

Billing facts: Every server is billed by the hour: the plan’s hourly rate is deducted from the server’s prepaid balance for every hour it exists, powered on or off, until you delete it. A stopped server is still billed, because its vCPU, memory, disk and IP addresses stay reserved for you; only deleting the server stops billing. Each new server is ordered with an initial credit, prepaid and used for that server’s hours. To pause the bot, stop the service with sudo systemctl stop discord-bot; to stop paying, delete the server.

Bigger plans are on the pricing page, and the VPS cost calculator models any mix of hours and plans. To see why a capped hourly bill never costs more than a monthly one, and how hosts without a cap compare, read hourly vs monthly VPS billing. To compare hosts, how much a VPS costs lists sourced prices from 20 providers.

What to do if your Discord bot token leaks

A leaked token gives full control of the bot in every server it is in. Treat any token that appeared in a commit, a screenshot, a paste, a log or a chat as leaked, and act in this order:

  1. Reset it. Developer Portal, your app, Bot, Reset Token. The old token stops working.
  2. Deploy the new one. Edit the file with sudoedit /etc/discord-bot/bot.env, then run sudo systemctl restart discord-bot.
  3. Check the damage. In each server, open Server Settings, Audit Log, and filter by the bot to see what it did.
  4. Find the source. If it was a Git commit, the reset already neutralized it; rewriting history is cleanup, not the fix. GitHub’s secret scanning lists Discord bot tokens as a partner pattern, so tokens pushed to public repositories get reported to Discord, but scrapers watch public repositories too, so do not wait for that.
  5. Assume the worst if you cannot explain it. If the token may have come off the server itself, deploy a fresh VPS, harden it, redeploy from Git with a new token, then delete the old server after the checklist before you delete a VPS.

Prevention is cheap: least-privilege permissions on the install link, token and .env files listed in .gitignore, two-factor authentication on the Discord account that owns the app, and never pasting the token into Discord, a pastebin, an AI chat or a screenshot.

Deploy this setup

Run this Discord bot 24/7

Quartz Q1 · 1 shared vCPU · 1 GB RAM · 25 GB NVMe · Istanbul

  • Per hour$0.01/hour
  • Per day (24 h)$0.24/day
  • Monthly cap$5.00/monthFor this job
Deploy Quartz Q1

Starts with a $5 initial credit, which goes into the server’s balance and pays for its hours.

Billed by the hour, never more than $5.00 per billing period. Delete the server and billing stops.

FAQ

Can I host a Discord bot for free?

You can run one on your own PC or a Raspberry Pi with no hosting bill, and some app platforms have free tiers. A Gateway bot stays online only while that machine or process stays up, and free tiers change their idle and sleep rules often, so read them before relying on one.

Does a Discord bot need to run 24/7?

A Gateway bot is offline whenever its process stops, so it must run continuously to respond. A bot that only uses an Interactions Endpoint URL needs a reachable HTTPS endpoint instead of a permanent connection.

Can I run several Discord bots on one VPS?

Yes. Give each bot its own user, token file and systemd unit, or one template unit such as [email protected], and size the RAM for the sum of their peaks.

Why does my Discord bot keep going offline?

Usually the process crashed, the server rebooted without the service enabled, or the token was reset. Run systemctl status discord-bot and journalctl -u discord-bot to see which one.

How do I know if my Discord bot has passed 10,000 users?

You cannot look it up: Discord shows no user count anywhere in the Developer Portal. Discord tells the app owner by email or system DM, with an alert on the app's portal page, and from that notice you have 90 days to apply while the bot keeps working.

Should I write my Discord bot in Python or Node.js?

Both are mature and run the same way under systemd. As of October 2026 the current releases are discord.py 2.7.1, which needs Python 3.8 or newer, and discord.js 14.27.0, which needs Node.js 18 or newer; Node.js 18 is past its upstream end of life, so install Node.js 24 LTS. Pick the language you already know.

Do I need to open ports or buy a domain for a Discord bot?

Not for a Gateway bot: it only makes outbound connections to Discord, so the firewall can stay closed except for SSH. You need a public HTTPS URL only if you switch to an Interactions Endpoint URL.

Sources

  1. Gateway (intents, privileged intents, session start limit, sharding)Discord Developer Documentation · docs.discord.com · checked
  2. Getting Started with Privileged Intent ReviewDiscord Developer Documentation · docs.discord.com · checked
  3. Changes to Privileged Intent Access for Discord AppsDiscord Developer Support · support-dev.discord.com · checked
  4. Receiving and Responding to InteractionsDiscord Developer Documentation · docs.discord.com · checked
  5. discord.py API Reference (Client, run, max_messages, latency)discord.py · discordpy.readthedocs.io · checked
  6. discord.js 14.27.0 packagenpm · npmjs.com · checked
  7. systemd.service (Type=, Restart=, RestartSteps=)freedesktop.org · freedesktop.org · checked
  8. systemd.exec (EnvironmentFile=, LoadCredential=, sandboxing)freedesktop.org · freedesktop.org · checked
  9. Node.js release scheduleNode.js Release Working Group · github.com · checked
  10. Ubuntu Server system requirementsCanonical · ubuntu.com · checked
All posts