Why you want to do that?

If you run a server exposed to the internet (a VPS, a home lab box, a Raspberry Pi behind a tunnel), people will try to log in. Most of them are bots, but the day a real login happens that you didn’t initiate, you want to know about it immediately.

I keep my phone with Discord on it all the time, so a Discord ping is the fastest way for me to know “hey, someone just SSH’d into your machine”. No need to open a dashboard or grep through /var/log/auth.log. The same trick works for Slack, Telegram or a plain email, but Discord webhooks are dead simple, so that’s what we will use.

Prerequisite

  • A Linux machine you can sudo on (I’m doing this on Ubuntu/Debian, same idea works elsewhere).
  • A Discord server where you can create a webhook.
  • Basic understanding of SSH.

Step 1: Create a Discord webhook

Open your Discord server, go to Server Settings → Integrations → Webhooks → New Webhook. Pick a channel where you want the alerts to land, give it a name like ssh-alerts, and click Copy Webhook URL.

It will look something like this:

https://discord.com/api/webhooks/123456789012345678/AbCdEf-your-very-long-token-here

Keep this URL secret. Anyone with it can post messages to your channel.

Step 2: Write the notification script

Now we write a small script that PAM will call on every session event. It reads the variables PAM exposes about the session and posts a formatted Discord embed. We handle both open_session and close_session, so you get a ping for logins and logouts, with the title changing to match the event.

/usr/local/bin/ssh-login-alert.sh

#!/bin/bash

# Discord webhook URL
WEBHOOK_URL="https://discord.com/api/webhooks/123456789012345678/AbCdEf-your-very-long-token-here"

EVENT_TYPE="$PAM_TYPE"
USER_NAME="$PAM_USER"
REMOTE_HOST="$PAM_RHOST"
SERVICE="$PAM_SERVICE"
HOSTNAME=$(hostname)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

if [ "$PAM_TYPE" = "open_session" ]; then
    TITLE="SSH Login"
elif [ "$PAM_TYPE" = "close_session" ]; then
    TITLE="SSH Logout"
else
    TITLE="SSH Event"
fi

curl -s \
  -H "Content-Type: application/json" \
  -X POST \
  -d "{
    \"embeds\": [{
      \"title\": \"$TITLE\",
      \"description\": \"User: $PAM_USER\nHost: $HOSTNAME\nservice: $SERVICE\nRemote IP: $PAM_RHOST\nTime: $(date '+%Y-%m-%d %H:%M:%S')\"
    }]
  }" \
  "$WEBHOOK_URL" >/dev/null 2>&1

Make it executable and lock down the permissions, since it holds your webhook URL:

$ sudo mv ssh-login-alert.sh /usr/local/bin/ssh-login-alert.sh
$ sudo chown root:root /usr/local/bin/ssh-login-alert.sh
$ sudo chmod 700 /usr/local/bin/ssh-login-alert.sh

The interesting bit here is the PAM_* variables. When PAM runs an external program, it passes the session details through the environment, so we don’t have to parse any logs:

  • PAM_USER — the user that just logged in
  • PAM_RHOST — the IP/host the connection came from
  • PAM_SERVICE — the service (will be sshd for SSH)
  • PAM_TYPE — the kind of PAM event (open_session, close_session, etc.). We use this to pick the title, so a login shows “SSH Login” and a logout shows “SSH Logout”.

Step 3: Hook the script into PAM

This is where the magic happens. PAM (Pluggable Authentication Modules) lets us run our script as part of the SSH session setup using the pam_exec.so module.

Open the SSH PAM config:

$ sudo nano /etc/pam.d/sshd

Add this line at the end of the file:

/etc/pam.d/sshd

# Send a Discord alert on every SSH session open and close
session optional pam_exec.so seteuid /usr/local/bin/ssh-login-alert.sh

A few things worth knowing about this line:

  • session means it runs when a session is opened and closed, which is exactly when someone logs in or out — both of which our script reports.
  • optional is important. If you use required and your script (or Discord) ever fails, you could lock yourself out. With optional, a failed alert never blocks the login.
  • seteuid runs the script with the effective user id of the calling process, which keeps curl and networking happy.

That’s it. No need to restart sshd, PAM picks up the config change on the next login.

Step 4: Test it

Open a new terminal and SSH into the machine:

$ ssh user@your-server

Within a second or two you should see an embed land in your Discord channel:

SSH Login
User: user
Host: my-server
service: sshd
Remote IP: 49.36.xxx.xxx
Time: 2026-06-23 21:34:11

And when you log out, a matching SSH Logout embed shows up the same way.

If nothing shows up, run the script manually to check the webhook works, then check /var/log/auth.log for any pam_exec errors:

$ sudo PAM_TYPE=open_session PAM_USER=test PAM_RHOST=1.2.3.4 PAM_SERVICE=sshd /usr/local/bin/ssh-login-alert.sh
$ sudo grep pam_exec /var/log/auth.log

A note on what this is and isn’t

This is a notification, not a defense. It tells you after someone has already logged in, so it doesn’t replace key-only auth, fail2ban, or a properly locked down sshd_config. But as a “second pair of eyes” on your machine it’s fantastic, and it took us all of three small steps to set up.

You can extend the script easily, for example only alert when the login is not from your home IP, or include the output of last to show recent sessions. But even the basic version above has saved me more than once from a “wait, that wasn’t me” moment.