If someone logs into your server, you want to know immediately, especially if it wasn’t you. A small script hooked into PAM can send an alert on every SSH login with the user, source IP and time, by email, Telegram, Slack, Discord or any webhook. It takes ten minutes to set up and gives early warning of stolen keys or passwords.

How it works

PAM (Pluggable Authentication Modules) runs for every login. The pam_exec module can run a script when a session opens, after successful authentication. The script receives details in environment variables:

Variable Contains
PAM_USER Username
PAM_RHOST Remote IP
PAM_TYPE open_session / close_session
PAM_SERVICE sshd
PAM_TTY Terminal

1. Write the notification script

Telegram

Create a bot with @BotFather, and get your chat ID. Then:

sudo tee /usr/local/bin/ssh-login-notify > /dev/null <<'EOF'
#!/bin/sh
[ "$PAM_TYPE" = "open_session" ] || exit 0

BOT_TOKEN="123456:ABC-your-bot-token"
CHAT_ID="123456789"
HOST="$(hostname)"
TEXT="SSH login on ${HOST}: user ${PAM_USER} from ${PAM_RHOST} at $(date '+%F %T %Z')"

curl -fsS -m 5 "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
  --data-urlencode "chat_id=${CHAT_ID}" --data-urlencode "text=${TEXT}" > /dev/null 2>&1 &
exit 0
EOF
sudo chmod 700 /usr/local/bin/ssh-login-notify

Slack, Discord or a generic webhook

Replace the curl line:

curl -fsS -m 5 -H 'Content-Type: application/json' \
  -d "{\"text\":\"${TEXT}\"}" "https://hooks.slack.com/services/XXX/YYY/ZZZ" > /dev/null 2>&1 &

Discord uses {"content": "…"}. ntfy.sh and Gotify are simple self-hostable options. See Telegram bot on a VPS.

Email

With a configured MTA (or msmtp):

printf 'Subject: SSH login on %s\n\n%s\n' "$HOST" "$TEXT" | /usr/sbin/sendmail [email protected] &

Important details

  • Run the request in the background (&) with a timeout (-m 5) so a slow or unreachable API never delays or blocks logins.
  • Always exit 0. With optional in PAM a failure won’t block logins anyway, but don’t rely on that.
  • chmod 700, owned by root. The script contains your bot token.

2. Hook it into PAM

Add one line at the end of /etc/pam.d/sshd:

session optional pam_exec.so /usr/local/bin/ssh-login-notify

optional means a failing script never prevents a login. No sshd restart is needed. PAM reads the file on each login.

Test it from a new terminal (keep your current session open): ssh user@server. The alert should arrive within a second or two.

To alert on sudo use too, add the same line to /etc/pam.d/sudo. PAM_RHOST is empty there, but PAM_USER and PAM_RUSER (the invoking user) are set.

3. Avoid alert fatigue

If you log in many times a day, or automation logs in constantly, alerts become noise and get ignored. Options:

  • Skip trusted sources (your office IP, the backup server):
case "$PAM_RHOST" in
  203.0.113.50|10.0.0.*) exit 0 ;;
esac
  • Skip service users ([ "$PAM_USER" = "backup" ] && exit 0).
  • Add geolocation or reverse DNS to the message, so unusual sources stand out.
  • Send to a channel you actually watch.

Alternatives and complements

  • /etc/ssh/sshrc — runs for every SSH session too (unless a user’s ~/.ssh/rc overrides it). It’s simpler, but users can bypass it, so the PAM method is more robust.
  • Log-based alerting — ship auth.log/journal to central logging and alert there. It’s harder to tamper with than a local script. See rsyslog.
  • Fail2Ban for failed attempts. Login notifications cover successful ones. See Fail2Ban.
  • Review logins regularly: last -a | head, lastb (failed), and journalctl -u ssh --since today.

If you get an alert you don’t recognise

  1. Check active sessions: who, w. Kill a suspicious session with pkill -KILL -t pts/1.
  2. Check ~/.ssh/authorized_keys for every user, plus new users in /etc/passwd.
  3. Rotate keys and passwords, and block the source IP.
  4. Investigate how they got in. If root was compromised, rebuild the server. See rkhunter and Linux rescue mode.

Quick reference

Step What
Script /usr/local/bin/ssh-login-notify (root, 700)
PAM hook session optional pam_exec.so /usr/local/bin/ssh-login-notify in /etc/pam.d/sshd
Variables PAM_USER, PAM_RHOST, PAM_TYPE
Don’t block logins background &, -m 5 timeout, optional
Recent logins last -a, lastb

Related: key-only SSH and SSH 2FA.

  • SSH
  • Security
  • Monitoring