Disable SSH Root Login
Forces PermitRootLogin no so the root account can never authenticate directly over SSH.
Checked against the resolved running state (e.g. sshd -T, sysctl, systemctl show), catches drop-ins and Includes a file read would miss. Caveat: runtime ≠ persistence; a value correct now may not survive a reboot.
Pavois asserts the effective configuration, the live, resolved state, not a file. File-based scanners (OVAL/SCAP, Lynis) miss Includes, drop-ins and runtime defaults; this check sees what is actually applied.
A mapping is a cross-reference to where each standard places this requirement, anchored and cross-validated, not a claim of equivalence. A passing check is evidence toward these references, how to read it.
Why this rule matters
Allowing direct root SSH login removes individual accountability (everyone logs in as the same all-powerful account) and makes root a single, well-known target for online password-guessing and brute-force attacks. Requiring users to log in under their own account and then escalate via sudo provides per-user audit trails, enables least-privilege, and removes the most valuable credential from the network-facing login surface.
What Pavois checks
Pavois reads the effective value from sshd -T, after all Includes, /etc/ssh/sshd_config.d/ drop-ins and Match blocks. Cloud images frequently ship a drop-in that re-enables PermitRootLogin prohibit-password; only the resolved output catches it, where a read of /etc/ssh/sshd_config would report a stale value.
describe command('sshd -T') do
its('stdout') { should match(/^permitrootlogin\s+no$/i) }
endHow to verify it is applied
Run sshd -T | grep -i '^permitrootlogin'. Expected output is permitrootlogin no. To resolve a Match block, test the value for a specific context with sshd -T -C user=deploy,host=app1,addr=10.0.0.5. A global permitrootlogin no with a narrow, intentional Match exception is not a finding (a common false positive); a Match block that re-opens root broadly is the real gap.
Inspect & investigate
Root login attempts (accepted or refused) are logged in /var/log/auth.log (Debian/Ubuntu) or journalctl -u sshd (RHEL family); a blocked attempt shows as ROOT LOGIN REFUSED or a failed authentication for root.
Remediation
Pavois's harden plan writes the sshd_setting directive PermitRootLogin no in a managed drop-in, validates with sshd -t, then reloads the ssh service. Apply it with pavois harden apply.
Pavois applies this with its own harden engine, the plan below, not a shell script:
| directive | permitrootlogin |
|---|---|
| notify | action: reload, service: ssh.service |
| resource | sshd_setting |
| value | no |
| verify | sshd -t -f %{path} |
pavois harden plan localwhere the target is local, a user@host SSH alias, or a container , Docs
Impact & precautions
Lockout risk, read before applying. Ensure at least one non-root account can log in over SSH and has working sudo/su to reach root; otherwise disabling root SSH locks you out entirely. Verify with a second, separate SSH session as that account before closing your current one. Watch for automation (backup jobs, Ansible, CI) that connects as root over SSH, it will break and must be repointed to a privileged user with sudo. The reload keeps current sessions alive, so an active root session is not killed immediately.
Standards mapping
| Standard | Reference | Type | Version | Confidence |
|---|---|---|---|---|
| ANSSI BP-028 | R33 | direct | 2.0 | high |
| CIS | 2.2.6, 5.1.20, 5.1.22 | direct | per OS, see the benchmark table | high |
| NIST | 3.1.1, AC-17(a), AC-6(2), CM-6(a), CM-7(a), CM-7(b), IA-2, IA-2(5) | supporting | 800-53 Rev 5 · 800-171 Rev 2 (pinned) | medium |
| PCI DSS | 2.2.6 | supporting | 4.0.1 | medium |
Each reference is a cross-reference anchored in the upstream benchmark and cross-validated against the SCAP Security Guide and ansible-lockdown, not a claim of equivalence. Direct = a prescriptive, line-level requirement; supporting = an abstract control family (NIST) the check provides evidence toward. How to read a mapping.