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 runs | Keeps a Gateway connection? | Good fit for | Watch out for |
|---|---|---|---|
| Your own PC or laptop | Only while it is on and awake | Development | Sleep, reboots, home connection drops |
| Raspberry Pi or home server | Yes, while power and internet stay up | A hobby bot | Power and ISP outages; you maintain the hardware |
| Free app-hosting tiers | Depends on the platform’s idle rules | Trying an idea | Idle and usage limits vary and change |
| Serverless functions (for example Cloudflare Workers) | No: code runs per request | Slash-command-only bots on an Interactions Endpoint URL | No message, member, reaction or voice events |
| VPS | Yes, 24/7 | Any Gateway bot, voice, scheduled jobs, a local database | You handle updates and security (covered below) |
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
- Open the Discord Developer Portal, choose New Application and give it a name.
- 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.
- On the same page, leave the Privileged Gateway Intents switched off unless your code needs them (see the next section).
- On the Installation page, under Guild Install, add the
applications.commandsandbotscopes and only the permissions the bot needs, such as Send Messages. Avoid Administrator. - 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:
| Setting | Why it is there |
|---|---|
Type= | systemctl 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_ from the root-only file; a missing file stops the start. |
Environment= | Without it, Python block-buffers stdout under systemd, so print() lines reach the journal late, or never if the process is killed. |
Restart= | Restarts 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=, RestartSteps=, RestartMaxDelaySec= | Exponential backoff: waits of about 10 s, 20 s, 39 s, 77 s and 152 s, then 5 minutes per attempt. |
MemoryHigh=, MemoryMax= | Guard 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=, ProtectHome, PrivateTmp | Read-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= and write to /var/. |
Wants= and After= | Starts the bot after the network is up at boot. |
WantedBy= | Lets systemctl enable start the bot at every boot. |
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:
| Option | After a crash | After a reboot | Logs | Fits when |
|---|---|---|---|---|
screen or tmux | Stays offline | Stays offline | Terminal scrollback only | Watching a quick test run |
| pm2 | Restarts it | Starts it only after pm2 startup and pm2 save; on Ubuntu, pm2 generates a systemd unit for itself | pm2 logs | You already manage Node.js apps with pm2 |
| systemd (this guide) | Restarts it, with backoff | Starts it (systemctl enable) | journalctl | Any language, nothing extra to install |
| Docker Compose | Restarts it with restart: always or unless-stopped | Starts it when the Docker service starts | docker compose logs | You already run containers |
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.
| Goal | Command |
|---|---|
| Follow the log live | sudo journalctl -u discord-bot -f |
| Last 100 lines | sudo journalctl -u discord-bot -n 100 --no-pager |
| Only the last hour | sudo journalctl -u discord-bot --since "1 hour ago" |
| Only since the last boot | sudo journalctl -u discord-bot -b |
| Find tracebacks and errors | sudo journalctl -u discord-bot --no-pager | grep -E "Traceback|Error" |
| See how much disk the journal uses | sudo journalctl --disk-usage |
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 see | Likely cause | Fix |
|---|---|---|
LoginFailure: Improper token has been passed. (discord.py) or TokenInvalid: An invalid token was provided. (discord.js) | Wrong token, or the token was reset | Copy a fresh token into bot. with sudoedit, then restart |
KeyError: 'DISCORD_ (discord.py) | The variable never reached the process. In discord.js the same mistake shows up as TokenInvalid | Check 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. | ExecStart= uses the system Python instead of the venv, or the dependencies were never installed in the app folder | Point ExecStart= at /opt/; rerun the pip or npm install as discordbot |
PrivilegedIntentsRequired or Used disallowed intents | The code requests an intent that is off in the portal | Enable 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= keeps the disk read-only | Add StateDirectory= and save data under /var/ (systemd passes the path as $STATE_) |
status= | The program in ExecStart= is missing or not executable | Check /opt/ or /usr/ |
status= | WorkingDirectory= is missing or unreadable for the bot user | Fix 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 number | A crash loop | Read the lines just above it for the real error |
| The slash command never appears | The sync failed or never ran, or the Discord app has not refreshed its command list | Check the log for sync errors, then reload Discord. The bot scope already includes applications. |
| Discord says the application did not respond | The bot is offline, or took longer than 3 seconds | Check the service; defer the response for slow commands |
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 profile | What drives resources | Start on |
|---|---|---|
| Slash-command or utility bot, Guilds intent only, a handful of servers | One runtime plus default caches | Quartz Q1: 1 vCPU, 1 GB RAM, 25 GB NVMe |
| Bot plus a database or a web dashboard, or two or three small bots | Extra processes | Quartz Q2: 1 vCPU, 2 GB RAM, 50 GB NVMe |
| Moderation or logging bot with Server Members or Presence intents in large servers | Member and presence caches grow with member count | Quartz 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 guilds | Audio encoding CPU, a second runtime, several shards | Quartz Q8 (4 vCPU, 8 GB) or Chrono C8 (2 dedicated vCPU, 8 GB) |
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
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.
| Duration | Hours on the meter | Cost $0.01 | Note |
|---|---|---|---|
| 8 hours | 8 | $0.08 | |
| 1 day | 24 | $0.24 | |
| 7 days | 168 | $1.68 | |
| 30 days | 720 | $5.00 | Capped at the monthly price |
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:
- Reset it. Developer Portal, your app, Bot, Reset Token. The old token stops working.
- Deploy the new one. Edit the file with
sudoedit /etc/discord-bot/bot.env, then runsudo systemctl restart discord-bot. - Check the damage. In each server, open Server Settings, Audit Log, and filter by the bot to see what it did.
- 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.
- 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
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.



