Christopher Ferrari

Real-Time Server Monitoring Through Discord

Published on May 28, 2025

My server sees hundreds of malicious scans a day. Logging's coveredโ€”I use GoAccess for NGINX traffic and keep an eye on /var/log/auth.log for SSH. Fail2ban handles brute force attempts and scans well enough. But I wanted a way to know what's happening without having to SSH in and check manually. Just something simple that tells me who's getting banned or when someone logs in.

So I hooked it into Discord.

How I Did It (and Why)

The first thing I wanted was SSH login alerts. I'm already logging logins in /var/log/auth.log, so instead of setting up something bloated like an email server or PAM modules that barely work under WSL, I wrote a simple shell script that tails the auth log and sends a Discord message when someone logs in successfully over SSH.

Here's the complete SSH monitoring script at /usr/local/bin/ssh-log-watcher.sh:

bash#!/bin/bash
LOGFILE="/var/log/auth.log"
WEBHOOK_URL="https://discord.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN"

tail -n0 -F "$LOGFILE" | while read LINE; do
echo "$LINE" | grep "Accepted" &> /dev/null
if [ $? -eq 0 ]; then
USER=$(echo "$LINE" | awk '{print $4}')
IP=$(echo "$LINE" | awk '{print $6}')
HOST=$(hostname)
TIME=$(date)

curl -H "Content-Type: application/json" -X POST -d "{\\"content\\": \\"๐Ÿ” SSH Login Detected\\\\n๐Ÿ‘ค User: $USER\\\\n๐ŸŒ IP: $IP\\\\n๐Ÿ’ป Host: $HOST\\\\n๐Ÿ•’ Time: $TIME\\"}" "$WEBHOOK_URL"
fi
done

The script grabs the username, IP, current time, and hostname, then uses curl to POST a message to the Discord webhook. I made it a background systemd service so it starts on boot and stays up:

[Unit]
Description=SSH Login Monitor
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/ssh-log-watcher.sh
Restart=always
RestartSec=5
User=root
Environment=DISCORD_WEBHOOK_URL_FILE=/etc/ssh-monitor/webhook.conf

[Install]
WantedBy=multi-user.target

For the SSH login alerts, the full script lives at: /usr/local/bin/ssh-log-watcher.sh. I made it executable and then created a systemd service at: /etc/systemd/system/ssh-log-watcher.service

That service just calls the script on boot and restarts it if it ever dies. You'll also want to run:

bashsudo systemctl daemon-reload  # Reloads systemd to pull in new service files
sudo systemctl enable ssh-log-watcher # Enables the service to start on boot
sudo systemctl start ssh-log-watcher # Starts the service right away, no reboot

This means every successful login, from anywhere, immediately alerts me on Discord with something like:

๐Ÿ” SSH Login Detected
๐Ÿ‘ค User: chris
๐ŸŒ IP: 192.168.1.10
๐Ÿ’ป Host: ehchris
๐Ÿ•’ Time: Thu May 29 08:42:17 CDT 2025

No delay, no need to manually read a log, no guessing. It works even if I'm not SSH'd in, which is the whole point.

Fail2ban Integration

Then I wired up Fail2ban to do the same. I already had a jail for SSH brute-force attempts and one for NGINX 404 spam. I created a custom Fail2ban action file at /etc/fail2ban/action.d/discord-ban.conf that sends a webhook when a ban happens. It uses placeholders like <ip> and <name> from the jail context:

[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -H "Content-Type: application/json" -X POST \\
-d "{\\"content\\": \\"๐Ÿšซ IP Banned\\\\n๐ŸŒ IP: <ip>\\\\n๐Ÿ”’ Jail: <name>\\\\n๐Ÿ•’ Time: $(date)\\\\\\n๐Ÿ’ป Host: $(hostname)\\\\\\n๐Ÿ“… Unban: $(date -d '+86400 seconds')\\"}" \\
$(cat /etc/fail2ban/discord-webhook.conf 2>/dev/null || echo "WEBHOOK_URL_NOT_CONFIGURED")
actionunban =

The actionban line does the heavy lifting by firing the webhook with the IP, jail name, and calculated unban time.

To wire that into my actual jails, I dropped two override files into:

/etc/fail2ban/jail.d/sshd.conf
/etc/fail2ban/jail.d/nginx-404.conf

Each one just contains the basic jail settings (enabled, port, logpath, etc.) plus:

action = discord-ban

Fail2ban triggers that hook every time someone gets banned. I get a nice clean log of blocked bots in a second Discord channel like:

๐Ÿšซ IP Banned
๐ŸŒ IP: 185.232.67.98
๐Ÿ”’ Jail: nginx-404
๐Ÿ•’ Time: Thu May 29 09:14:32 CDT 2025
๐Ÿ’ป Host: ehchris
๐Ÿ“… Unban: Fri May 30 09:14:32 CDT 2025

And yeah, I split the webhook URLs so SSH logins and Fail2ban bans go to different Discord channels. Way easier to keep high-signal alerts (like logins) separate from the noise of constant bot bans. That just means generating two Discord webhooks, one per channel, which takes five seconds and makes life significantly cleaner.

Security Considerations

Webhook URL Protection: Keep your Discord webhook URLs in separate config files with restricted permissions rather than hardcoding them in scripts:

bashsudo chmod 600 /etc/ssh-monitor/webhook.conf
sudo chmod 600 /etc/fail2ban/discord-webhook.conf

Error Handling: The scripts include basic fallbacksโ€”if Discord is unreachable or the webhook fails, the monitoring continues without crashing. Failed webhook calls don't interrupt log processing, though you might want to add local logging for webhook failures if uptime is critical.

Performance Under Load: On busy servers, the tail -F approach handles moderate traffic well. If you're seeing thousands of login attempts per minute, consider switching to a log aggregation tool, but for typical server loads this approach is lightweight and reliable.

Results

SSH Login

After running this setup for several weeks, it's completely changed how I monitor server activity. Instead of periodic SSH check-ins, I get real-time awareness of what's happening. The separate channels mean I can mute the ban notifications when I don't need the constant bot chatter, but keep login alerts at full volume.

The system has caught several legitimate security events I would have missed with manual log checking, and the immediate notification means I can respond to issues in minutes rather than hours. Plus, having the historical record in Discord makes it easy to spot patterns or reference past incidents.

Banhammer

For something that took maybe an hour to set up, the operational awareness it provides is invaluable. No complex monitoring stack requiredโ€”just Discord, some shell scripting, and systemd doing what it does best.