Self-Hosting a Discord Bot: The Complete 2026 Guide
Table of Contents
You’ve built a Discord bot. It works on your laptop. Now you need to keep it running 24/7 without leaving your laptop on or paying AWS / Heroku / Railway prices that balloon as soon as you cross a free-tier threshold. This guide walks through your options, picks the right Witchly tier for each language, and gets your bot online in under 15 minutes.
The three hosting paths
When it comes to 24/7 bot hosting, you have three realistic options:
- Run it at home — cheap, but dies the moment your power blips or ISP maintains. Bad for anything you care about.
- PaaS platforms (Heroku, Railway, Fly.io) — easy to start, but bot-type workloads (long-lived processes with websocket connections) often trigger auto-sleep and cold starts. Pricing also scales surprisingly fast.
- VPS or dedicated hosting — bot stays online, you control everything, and the price stays flat.
Witchly falls into option 3 — except instead of giving you raw SSH access to a VPS, we give you a managed container with a panel, console, file manager, backups, and all the operational pieces you’d otherwise have to bolt on.
Pick your language
Your existing bot is already in a language. But if you’re starting fresh, here’s a practical take:
| Language | Best for | Memory footprint |
|---|---|---|
| Node.js | Most bots. Largest ecosystem (discord.js is the default). | 100–300 MB |
| Python | Data-heavy bots, ML integrations, quick scripts. discord.py or py-cord. | 80–200 MB |
| Bun | Modern TypeScript setups, 2–4× faster than Node. | 50–150 MB |
| Go | Performance-critical bots, low memory. discordgo. | 20–80 MB |
| Rust | Max performance, max safety. Serenity or Twilight. | 20–100 MB |
| Elixir | Massive concurrent bots, supervisor-based reliability. Nostrum. | 60–200 MB |
| Luvit | Lua fans. Discordia. | 40–100 MB |
| Java | JDA or Discord4J bots, enterprise-y setups. | 200–500 MB |
For 95% of bots, Node.js, Python, or Bun are the right pick. They have the largest libraries, the best Discord documentation, and the easiest debugging story.
Plan sizing
Our Language plans start at $2/mo (The Script, 512 MB RAM). Most Discord bots fit comfortably here — a discord.js bot with 10 commands handling 50 servers uses ~150 MB. Scale up to The Runtime ($4/mo, 1 GB RAM) if:
- You’re using Lavalink for music (audio processing eats RAM)
- You cache a lot in memory (image cache, leaderboards, etc.)
- You have hundreds of servers and MESSAGE_CONTENT intent
For thousands of servers, look at The Compiler ($6/mo, 2 GB) or even a Game-tier Elite plan if you’re comfortable using those resources for a non-game workload.
If you want to start free: the free tier is available for all 11 runtimes. It uses the same coin economy as game servers — renew every 7 days using coins you earn passively. Great for development and small bots.
Step-by-step: deploying a discord.js bot
Let’s do a complete walkthrough. We’ll deploy a tiny discord.js bot from a GitHub repo.
1. Prepare your repo
Your bot should have at minimum:
my-bot/
├── index.js # entrypoint
├── package.json # dependencies
└── .gitignore # exclude node_modules, .env
Example index.js:
import { Client, GatewayIntentBits } from "discord.js"
import "dotenv/config"
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
})
client.on("ready", () => console.log(`Logged in as ${client.user.tag}`))
client.on("messageCreate", (msg) => {
if (msg.content === "!ping") msg.reply("Pong!")
})
client.login(process.env.DISCORD_TOKEN)
package.json:
{
"name": "my-bot",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"dependencies": {
"discord.js": "^14.16.0",
"dotenv": "^16.4.0"
}
}
Push this to GitHub. Keep the repo public (or grab a personal access token for private deploys).
2. Deploy on Witchly
- Go to dash.witchly.host → Deploy.
- Pick the Languages tab.
- Select Node.js.
- Pick your tier — The Runtime ($4/mo) is a safe default.
- Click Deploy.
3. Configure
On the new server’s Startup tab:
| Variable | Value |
|---|---|
Git Repo Address | https://github.com/you/my-bot.git |
Main file | index.js |
Auto Update | 1 (toggle on so git push deploys on next restart) |
For private repos, also fill Git Username and Git Access Token (GitHub PAT with read-only repo scope).
4. Secrets
Don’t commit DISCORD_TOKEN to Git. Instead, upload a .env file to your server via SFTP (Files tab → .env):
DISCORD_TOKEN=your-bot-token-here
dotenv will pick it up when your bot starts.
Alternatively, set it as a Pterodactyl variable so it’s accessible to process.env.DISCORD_TOKEN at runtime without a .env file — but that requires editing the egg to add the variable.
5. Start
Head to the Console tab and click Start. You’ll see:
Installing dependencies...
added 47 packages in 3s
Logged in as MyBot#1234
Your bot is online. Invite it to your Discord server via the OAuth2 URL generator in the Developer Portal.
Keeping your bot alive
A few operational touches that separate a working bot from a reliable one:
Scheduled restarts
Bots can accumulate memory over days of uptime, even with no bugs. Schedule a restart every 24 hours from the Schedules tab. See Scheduled Restarts.
Backups
If your bot stores persistent data (levels, economies, warnings), enable the Backups tab and schedule daily snapshots. Lock important backups so they’re never auto-deleted.
Monitoring
Deploy Uptime Kuma on a second Witchly server and point a monitor at your bot’s Discord presence (via heartbeat webhook) or a health-check HTTP endpoint. Get Discord notifications when it goes down.
Database
For anything beyond tiny bots, pair with:
- Redis — caching, rate limits, cooldowns
- Postgres or MariaDB — user profiles, levels, economy balances
- MongoDB — when your data shape changes often
Host them on separate Witchly servers. They’re accessible over your server IP + port.
Common pitfalls
Missing intents
If your bot ignores messages or can’t see members, check Privileged Gateway Intents in the Discord Developer Portal. You probably need Message Content and/or Server Members.
Port binding
If your bot also runs a web server (status page, API), bind to 0.0.0.0:${process.env.SERVER_PORT} — never localhost or a hardcoded port. Only the Witchly-allocated port is reachable externally.
Process exits
If your bot starts and immediately exits with no error, it’s usually:
- Unhandled promise rejection killing the process → add
process.on("unhandledRejection", console.error) - Token is wrong → verify in the Developer Portal
Cost comparison
For a small bot running 24/7:
| Host | Cost/month | Notes |
|---|---|---|
| Witchly Language free tier | $0 | Renew every 7 days with coins |
| Witchly The Script | $2 | 512 MB, dedicated |
| Witchly The Runtime | $4 | 1 GB, best seller |
| Heroku Eco | $5 | 1000 dyno hours, sleeps after inactivity |
| Railway | $5+ | Pay-as-you-go, climbs with usage |
| AWS t3.micro | ~$8 | You manage everything yourself |
| DigitalOcean droplet | $6+ | You manage everything yourself |
Witchly is cheaper than managed PaaS and easier than a raw VPS. The coin economy on free tier means even serious dev use can cost $0 if you’re active on the dashboard.
Wrapping up
Self-hosting your bot is more reliable, cheaper, and more controllable than the default “deploy to Heroku” path most tutorials suggest. Witchly’s Language runtimes remove the ops overhead: Git-based deploys, a console to watch logs, SFTP for file tweaks, backups when you need them.
Pick your language, commit your code, hit deploy. Your bot is online in 5 minutes.
Need help picking a runtime, sizing a plan, or debugging a deployment? Drop into our Discord.