
Your bot going offline at 2 AM and nobody knowing until noon is the worst case for community trust. Monitoring catches outages while they are still fresh and minimises downtime before it becomes a user experience problem.
What to Monitor
Process health: Is the bot process running at all?
Discord connectivity: Is the WebSocket connected to Discord's gateway?
Response latency: Is the bot responding to commands within acceptable time?
Memory usage: Is memory growth indicating a leak approaching OOM?
PM2 Built-in Health Checks
PM2 provides basic monitoring via pm2 status and metrics via pm2 monit. For minimal setup, PM2's auto-restart already provides MTTR (Mean Time to Recovery) of < 5 seconds on crashes.
PM2 + Discord webhook notification (setup covered in PM2 guide) sends crash alerts directly to your moderation Discord.
Uptime Kuma: Free Self-Hosted Monitoring
Uptime Kuma is a self-hosted monitoring tool. It can run on the same VPS as your bot to watch the bot itself, but it cannot warn you when that VPS goes down, because it goes down with it (see the outside check below).
# Install with Docker, dashboard on localhost only
docker run -d --restart=always --network host \
-e UPTIME_KUMA_HOST=127.0.0.1 \
-v uptime-kuma:/app/data \
--name uptime-kuma \
louislam/uptime-kuma:2
--network host puts the container on the VPS's own network, so 127.0.0.1 inside Uptime Kuma is the VPS itself and it can reach the bot's health endpoint below. On Docker's default network, 127.0.0.1 would be the container's own loopback. UPTIME_KUMA_HOST=127.0.0.1 makes the dashboard listen on localhost only.
Do not publish the dashboard with -p 3001:3001. Docker's documentation warns that a published port is open to the outside world by default, and that Docker and ufw are incompatible: traffic to a published container port is routed before it reaches the chains ufw uses, so a ufw rule that blocks port 3001 does not protect it.
To open the dashboard, start an SSH tunnel from your own computer and browse to http://localhost:3001:
ssh -L 3001:127.0.0.1:3001 user@YOUR_VPS_IP
If you want the dashboard on a domain, put a reverse proxy with HTTPS (nginx or Caddy) in front of 127.0.0.1:3001 and keep Uptime Kuma's own login switched on. Uptime Kuma uses WebSockets, so the proxy has to pass the Upgrade and Connection headers.
Add a monitor:
- Monitor type: HTTP(S), if your bot has a health endpoint
- URL:
http://127.0.0.1:3000/health - Interval: Every 60 seconds
Adding a Health Endpoint to Your Bot
const express = require('express');
const app = express();
app.get('/health', (req, res) => {
if (!client.isReady()) {
return res.status(503).json({ status: 'disconnected' });
}
res.json({
status: 'ok',
ping: client.ws.ping,
guilds: client.guilds.cache.size,
uptime: process.uptime()
});
});
app.listen(3000, '127.0.0.1'); // Localhost only - don't expose publicly
Uptime Kuma hits this endpoint every minute. If it returns 503 or times out, Uptime Kuma sends you a notification immediately.
Add a Check From Outside the VPS
A monitor on the bot's own VPS cannot report that the VPS itself is down. If the VPS crashes, hangs or loses its network, Uptime Kuma stops or is cut off together with the bot, and it cannot send you an alert. So add one check that runs somewhere else: a second Uptime Kuma on another machine (for example a second VPS), or a hosted uptime monitoring service.
The simplest outside check is a heartbeat. Create a Push monitor in the second Uptime Kuma, set its heartbeat interval to 90 seconds, copy its push URL and let the bot call it every minute while it is connected to Discord. The interval is longer than the push on purpose: Uptime Kuma counts a push as missed when it arrives more than one second late, so with one minute on both sides a short network delay is enough for a false alarm.
// Heartbeat to a Push monitor on another machine (fetch is built into Node.js 18 and newer)
setInterval(() => {
if (client.isReady()) fetch(process.env.KUMA_PUSH_URL).catch(() => {});
}, 60000);
When the calls stop, because the bot lost Discord, the process died or the whole VPS is down, the outside Uptime Kuma marks the monitor down and alerts you. The bot only makes outgoing requests, so nothing on the bot's VPS has to be opened to the internet. The second Uptime Kuma does have to be reachable from the bot's VPS, for example behind the HTTPS reverse proxy described above.
Notification Channels
Configure Uptime Kuma to notify via:
- Discord webhook: Alert lands directly in your admin channel
- Telegram: Immediate phone notification
- Email: For team notification
Response times vs. notification channel:
- Critical outage: Telegram/Discord (seen within minutes)
- Non-critical degradation: Email (reviewed daily)