Monitoring SSH
sshd is recognised by its service unit on systemd hosts and by its process elsewhere, and
the monitoring starts within a minute with nothing to configure. The agent counts logins
and failures from what sshd already logs, reads the daemon's effective configuration for
the two doors attackers look for, checks the port, and hashes sshd_config so a change to
it never goes unnoticed. sshd itself is never touched.
How SSH is recognised
Four definitions produce the same metrics under the same names. On systemd hosts the match
is the service unit, not the process: ssh.service on Debian and Ubuntu, sshd.service
on the RHEL family. Modern systemd installs start sshd on demand from a socket, so there
is no sshd process to see until the first connection arrives, and a process match would
silently drop the whole service on exactly those hosts. The unit is there whether or not
a connection is open, and it is absent only when sshd is really disabled, so an OpenSSH
package installed but switched off produces no false alert. On OpenRC and sysvinit hosts
(ssh-openrc, ssh-sysvinit) the match is the running sshd process. Every minute the
agent:
- runs
sshd -T, which prints the configuration the daemon actually runs with (includes and defaults resolved) and yields the port,PermitRootLoginandPasswordAuthentication. Before the first connection on a socket-activated hostsshd -Tcannot run yet (its privilege-separation directory does not exist), so the agent readssshd_configitself instead, followingIncludedirectives and stopping at the firstMatchblock, with the daemon's defaults for anything not set; - connects to the port. The port comes from the live listener owned by
sshd. When the configuration names a port nothing listens on, the check says so and uses the active listener instead. Under socket activation, where systemd holds the socket and sshd owns no listener, the configured port is used once it has been read, 22 until then; - reads new authentication lines: the journal identifiers
sshdandsshd-sessionon systemd,/var/log/messageson OpenRC,/var/log/auth.logon sysvinit; - hashes
/etc/ssh/sshd_config.
What is collected
Login rates, per second: failed logins (a failed password, key or keyboard-interactive attempt, an invalid user, an authentication failure), accepted logins, and accepted logins for root.
Brute-force sources. Every failure also captures the source address. When one address reaches ten failures inside a sampling interval, the agent emits one event listing the top ten sources with their counts and the total, at most once every 30 minutes. The signal names who is hammering the host, not only how fast.
Configuration doors. Root reachable by password, and PasswordAuthentication yes, in
the effective configuration, as 0/1 metrics. Root counts only when it can log in with a
password: PermitRootLogin yes together with PasswordAuthentication or keyboard-interactive
authentication switched on (ChallengeResponseAuthentication on OpenSSH before 8.7).
PermitRootLogin yes with both switched off lets root in by key only, which is the same
state as prohibit-password and the default on Proxmox VE, where cluster nodes reach each
other as root over keys. Because sshd -T resolves the global view, a global no with a
scoped Match exception reads as closed instead of raising a false alarm. The resolved
values ride as facts (port, permitRootLogin, passwordAuth).
Integrity. A changed sshd_config hash raises an integrity event on the host.
| Signal | Metric |
|---|---|
| Failed logins per second | svc.ssh.auth_fail_rate |
| Accepted logins per second | svc.ssh.accepted_rate |
| Accepted root logins per second | svc.ssh.root_login_rate |
| Root can log in with a password | svc.ssh.root_login_permitted |
| PasswordAuthentication yes in effect | svc.ssh.password_auth_on |
Names, tags and units are in the metrics reference.
Logs. sshd's own lines ride the system log stream described on
Monitoring systemd, notice level and above by default. A connection
that drops before the key exchange (kex_exchange_identification), the signature of a
port scanner, is filed at notice rather than error so it never counts as a service error.
Alerts out of the box
| Rule | Fires when | Severity | Ships |
|---|---|---|---|
| SSH brute-force in progress | 0.3 or more failed logins per second for 2 minutes | warning | on |
| Root logged in over SSH | an accepted login for root | warning | on |
| sshd lets root log in with a password | the effective configuration lets root in with a password (fix: PermitRootLogin prohibit-password) | warning | on |
| sshd accepts passwords | the effective configuration says PasswordAuthentication yes | warning | on |
Background scanning on a public host fails a few times a minute; 0.3 per second is 18
failures a minute sustained for two minutes, which is a dictionary attack and not a typo,
and the incident closes once the rate falls back under 0.1. One accepted root login inside a
one-minute sample already sits above the 0.01 threshold, so that rule fires on the first
root session; where root over SSH is routine, switch it off per host
(see Alerting rules). The two door rules fire as long as the effective
configuration keeps the door open and close when it changes, and since sshd -T includes
defaults, a setting you never wrote still counts. Thresholds and hysteresis for each rule
are in the rules catalog.
Going deeper
- The Security module shows the attacking sources with their countries, accepted and root logins, and the integrity trail. Its hardening scan checks the same sshd doors alongside file permissions and kernel flags.
- Changes to
/etc/passwd,/etc/shadowand/etc/sudoersraise an incident; a change tosshd_configraises an event you see on the host and in the security timeline.
Limitations
- The rates are counted from what sshd logs. A quieter
LogLevelinsshd_configmeans fewer lines and lower rates. - The doors reflect the global effective configuration. Exceptions inside
Matchblocks are neither counted as open doors nor reported separately. - One port per host is checked: the listener owned by sshd, or the configured port under socket activation.
- The brute-force event names addresses; the agent never blocks them. Blocking stays with your firewall or fail2ban.
- On OpenRC and sysvinit hosts the log file paths are fixed (
/var/log/messages,/var/log/auth.log). A distribution that logs sshd elsewhere produces no rates. - On systemd the match needs the unit to carry one of the two standard names. A unit renamed by hand is not recognised.
Related
- Monitoring systemd for the journal the counters read
- Monitoring cron
- Monitoring pending updates and CVEs
- Features on the main site