π English Β· TiαΊΏng Viα»t
PAMSignal is a lightweight, zero-dependency login monitor for Linux servers. It watches the systemd journal for PAM authentication events and sends real-time alerts to your favorite messaging platforms.
If you manage a handful of servers and want to know instantly when someone logs in or tries to brute-force your machineβwithout deploying Wazuh, EDR, or reading 200 pages of documentationβthis is for you.
Think of it as a smoke detector for your servers' front door: it doesn't lock the door (hardening SSH is still your job β here's how), but it tells you the instant someone opens it or starts trying to force it.
- Solo devs & self-hosters β get a phone buzz the moment anyone logs into your VPS, or a bot starts hammering it. Two-minute setup, no platform to babysit.
- Small teams & startups β one
#security-alertschannel and one fleet dashboard for everyone on call. - Hosting providers & MSPs β offer per-customer login alerting as a near-zero-cost value-add for the servers you manage β hosting-provider playbook.
Full playbooks for each in Use Cases & Integrations.
Seconds after an event, a line like this lands in your Telegram, Slack, Teams, Discord, or WhatsApp:
[NOTICE] auth.login_success user=admin src=192.168.1.100:52341 host=web-01 service=sshd auth=publickey
[WARN] auth.login_failure user=root src=203.0.113.50:39182 host=web-01 service=sshd auth=password
[ALERT] auth.brute_force_detected src=203.0.113.50 attempts=12 window=300s user=root host=web-01
Prefer one screen for the whole fleet instead of per-host pings? PAMSignal also feeds a ready-made Grafana dashboard (preview below).
- Real-time Alerts: Native integration for Telegram, Slack, Teams, WhatsApp, Discord, and Custom Webhooks.
- Brute-Force Protection: Natively tracks failed attempts and seamlessly integrates with Fail2ban to block attackers.
- Ultra Lightweight: A single C binary with a single config file. The only dependency is
libsystemd. - Fault-Tolerant: Alert dispatching is isolated via
fork+exec. Network timeouts or API failures will never crash the core monitoring process. - Fits your stack: Speaks ECS JSON to any SIEM or webhook and ships a Grafana fleet dashboard β it feeds the tools you already run instead of being one more console.
PAMSignal is the detection layer of a four-layer defence-in-depth stack. It does that one job well and leaves the others to the right tool β it never modifies sshd or blocks anything itself.
| Layer | Job | Your tool |
|---|---|---|
| Prevention | make the door hard to open | SSH key-only auth β Secure SSH guide |
| Integrity | detect tampering with the system | AIDE / debsums / rpm -V |
| Detection / Alerting | tell you what's happening, now | PAMSignal |
| Forensics | reconstruct events after the fact | auditd + journald retention |
The natural companion is response: Fail2ban acts on PAMSignal's brute-force signal to block attacker IPs at the firewall. Exactly what PAMSignal can and can't observe is spelled out in the Threat Model.
graph LR
sshd["sshd / sudo / su"]
journald[("systemd-journald")]
pamsignal["PAMSignal"]
admin["π§βπ» Admin"]
platforms["Telegram / Slack<br/>Teams / WhatsApp / Discord<br/>Custom webhook"]
fail2ban["Fail2ban<br/>(iptables / ufw)"]
sshd -- "PAM auth events" --> journald
pamsignal -- "reads & writes<br/>structured events" --> journald
admin -- "journalctl -t pamsignal" --> journald
pamsignal -. "fork+exec curl<br/>(best-effort)" .-> platforms
platforms -. "alerts" .-> admin
fail2ban -. "watches pamsignal BRUTE_FORCE_DETECTED events<br/>& blocks attacker IP" .-> journald
style pamsignal fill:#2d6a4f,stroke:#1b4332,color:#fff
style journald fill:#264653,stroke:#1d3557,color:#fff
style sshd fill:#6c757d,stroke:#495057,color:#fff
style platforms fill:#6c757d,stroke:#495057,color:#fff,stroke-dasharray: 5 5
style fail2ban fill:#e76f51,stroke:#d62828,color:#fff,stroke-dasharray: 5 5
style admin fill:#e9c46a,stroke:#f4a261,color:#000
Debian / Ubuntu
sudo install -d -m 0755 /etc/apt/keyrings
curl -fsSL https://anhtuank7c.github.io/pamsignal/key.asc | sudo gpg --dearmor -o /etc/apt/keyrings/pamsignal.gpg
echo "deb [signed-by=/etc/apt/keyrings/pamsignal.gpg] https://anhtuank7c.github.io/pamsignal stable main" | sudo tee /etc/apt/sources.list.d/pamsignal.list
# Refresh only the PamSignal repo, so an unrelated broken repo can't block the install
sudo apt update -o Dir::Etc::sourcelist="sources.list.d/pamsignal.list" \
-o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"
sudo apt install pamsignalFedora / CentOS / RHEL
Fedora / CentOS
sudo dnf config-manager addrepo --from-repofile=https://anhtuank7c.github.io/pamsignal/rpm/fedora/pamsignal.repo
sudo dnf install pamsignalRHEL 9 / AlmaLinux 9 / Rocky Linux 9
sudo dnf config-manager --add-repo https://anhtuank7c.github.io/pamsignal/rpm/el9/pamsignal.repo
sudo dnf install pamsignalUbuntu 20.04 LTS (Focal, ESM-only)
20.04 is ESM-only since April 2025 and there's no gh-pages apt pocket for it β but a Focal-targeted .deb is built and tested in CI on every release, then attached as a GitHub release asset. Download and install directly:
VERSION=0.5.0 # bump per release β see https://github.com/anhtuank7c/pamsignal/releases
curl -fL -o pamsignal_focal.deb \
"https://github.com/anhtuank7c/pamsignal/releases/download/v${VERSION}/pamsignal_${VERSION}-1_focal_amd64.deb"
# Optional: verify the detached signature (signing key fingerprint below)
curl -fL -o pamsignal_focal.deb.asc \
"https://github.com/anhtuank7c/pamsignal/releases/download/v${VERSION}/pamsignal_${VERSION}-1_focal_amd64.deb.asc"
gpg --verify pamsignal_focal.deb.asc pamsignal_focal.deb
# Install β apt resolves libsystemd0 and other transitive deps from your host's apt sources
sudo apt install ./pamsignal_focal.debTo upgrade later, re-run the same recipe with the new VERSION. For a fleet, wrap it in a small Ansible / cron / shell script.
π Lifecycle reminder. Focal exits ESM in April 2030. Plan a migration to 22.04 LTS (Standard Support until April 2027) or 24.04 LTS in the next ~12 months. See docs/distros.md for the full support matrix.
Signing key fingerprint: 2D2C 828F A6F4 D019 E446 8FBB B106 2235 2862 2F69
Edit the configuration file (/etc/pamsignal/pamsignal.conf) and drop in your platform credentials. For example, to enable Telegram:
telegram_bot_token = <your_bot_token>
telegram_chat_id = <your_chat_id>See Alert Setup Guides for Slack, Teams, WhatsApp, and Discord.
Two tuning keys worth knowing on day one β one controls how often you get pinged, the other controls what you get pinged about:
# Minimum seconds between brute-force alerts for the same IP.
# Default 60. Set to 0 to fire on EVERY threshold crossing β
# useful for low-traffic hosts where you don't want any signal collapsed.
alert_cooldown_sec = 60
# Which event categories trigger chat alerts. Default is "all" (every category),
# which is noisy in production. Narrow to just what you care about β
# most operators only want successful logins and brute-force pings.
enable_notification_type = login_success,brute_forceAll six event-type tokens (login_success, login_failed, session_open, session_close, brute_force, all) are documented in Configuration β Notification-type filter. journalctl -t pamsignal keeps the full forensic trail regardless of what you filter out of chat.
Need to send alerts to a provider we don't support natively? Or want to build your own auto-banning logic? PAMSignal sends structured ECS JSON to any custom webhook.
π Check out the Node.js Custom Webhook Example to see how easy it is to build your own receiver!
Apply your configuration and watch the live events:
sudo systemctl reload pamsignal
journalctl -t pamsignal -fPAMSignal calculates brute-force thresholds for you. You can take this a step further by automatically blocking attackers' IPs using Fail2ban. Since PAMSignal does the heavy lifting, the Fail2ban setup is incredibly simple.
π Read the Fail2ban Integration Guide
First time securing SSH itself? Pair this with Secure SSH & Manage a Fleet β PAMSignal watches the door; that guide makes the door strong, and shows you how to drive many servers from one place.
Per-host journalctl and real-time chat alerts cover one host. For fleet-wide auth visibility β one queryable view across every server β there's a Loki/Alloy/Grafana integration that ships with PAMSignal: ECS-schema events flow into Loki via Alloy, and a single dashboard answers "what's happening with auth across my fleet right now?" at a glance.
Local try-it stack (no Linux fleet required):
cd examples/grafana && docker compose up -d
# β http://localhost:3000 (anonymous Admin, dashboard preloaded)π New to Grafana? Start with Grafana from Zero β what Grafana/Loki/Alloy even are, setup both ways (cloud or self-host), and how to read every panel.
π Read the Grafana Integration Guide β the production reference: full deploy + Alloy install + 4 alert rules
Guides β start here
- π§ Use Cases & Integrations β who it's for (solo Β· team Β· hosting provider) and how to plug PAMSignal into your existing stack
- π Secure SSH & Manage a Fleet β harden the door PAMSignal watches, and drive 1β50 servers from one
~/.ssh/config - π Grafana from Zero β stand up a fleet-wide dashboard and learn to read every panel, even if you've never used Grafana
Reference
- ποΈ Architecture β C4 diagrams, isolation models, and design decisions
- βοΈ Configuration β Config reference, CLI flags, and tuning
- π Alerts β Webhook payloads and channel setup
- π Deployment β Security hardening and systemd setup
- π― Threat Model β What pamsignal defends against, what it deliberately does not, and the design rationale behind the split
- π Grafana Integration β Design β Schema, label cardinality, and panel rationale (the deep dive behind the guide above)
- π§ Supported Distributions β Three-tier matrix (CI-tested / expected to work / unsupported) with reasoning per row
- π οΈ Development β Building from source and testing
- π Security Policy β Responsible-disclosure channel and supported versions
- π Changelog β Status, task tracking, and updates
This project would look very different β or wouldn't exist at all β without two friends:
![]() Nguyen HongΒ Quan @hongquan |
Gave the kind of honest, no-punches-pulled feedback on Linux standards and operator expectations that reshaped PAMSignal's roadmap and architecture. The single biggest design decision in this codebase β subscribing to |
![]() SamuelΒ Le @lehiep1994 |
Kept me reading and kept me building. He sent me books on Linux internals at exactly the moments I needed them, and the steady encouragement to not abandon this project β through every "is this even worth shipping?" stretch β is a real part of why PAMSignal made it to a release. π |
This project is built with AI assistance (Claude Code). I am open about this workflow: AI catches edge cases, guides architectural decisions, and even performed the OWASP ASVS 5.0 security review that hardened this project. The .claude/ directory is committed to this repo so you can inspect exactly how AI is utilized here. Humans test on real systems and take responsibility for shipping.



