Skip to main content

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, PermitRootLogin and PasswordAuthentication. Before the first connection on a socket-activated host sshd -T cannot run yet (its privilege-separation directory does not exist), so the agent reads sshd_config itself instead, following Include directives and stopping at the first Match block, 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 sshd and sshd-session on systemd, /var/log/messages on OpenRC, /var/log/auth.log on 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.

SignalMetric
Failed logins per secondsvc.ssh.auth_fail_rate
Accepted logins per secondsvc.ssh.accepted_rate
Accepted root logins per secondsvc.ssh.root_login_rate
Root can log in with a passwordsvc.ssh.root_login_permitted
PasswordAuthentication yes in effectsvc.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​

RuleFires whenSeverityShips
SSH brute-force in progress0.3 or more failed logins per second for 2 minuteswarningon
Root logged in over SSHan accepted login for rootwarningon
sshd lets root log in with a passwordthe effective configuration lets root in with a password (fix: PermitRootLogin prohibit-password)warningon
sshd accepts passwordsthe effective configuration says PasswordAuthentication yeswarningon

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/shadow and /etc/sudoers raise an incident; a change to sshd_config raises an event you see on the host and in the security timeline.

Limitations​

  • The rates are counted from what sshd logs. A quieter LogLevel in sshd_config means fewer lines and lower rates.
  • The doors reflect the global effective configuration. Exceptions inside Match blocks 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.