Remediation Guides

How to Disable SSH Root Login (CIS: Ensure SSH Root Login Is Disabled)

26 September 2026 11 min read

“Ensure SSH root login is disabled” is a CIS benchmark check that fails when the SSH daemon still lets the root account log in directly. To fix it, confirm a named admin account can log in and use sudo, set PermitRootLogin no at the top of /etc/ssh/sshd_config, verify with sshd -T, reload sshd, and rerun the audit.

The change itself is one line. What goes wrong is ordering: sshd keeps the first value it reads, so a drop-in file or a Match block can silently undo your edit, and a scan can pass while the running daemon still accepts root.

What the scanner is actually detecting

This finding comes from a Nessus compliance scan that runs a CIS benchmark audit file over an authenticated SSH session. It is a configuration check, not a vulnerability plugin, and it reports PASSED or FAILED for the PermitRootLogin setting. How the item is evaluated depends on the audit file. Some Tenable implementations run a command such as sshd -T -C … | grep permitrootlogin (for example CIS AlmaLinux OS 9 v2.0.0, 5.1.20, CMD_EXEC). Older ones, such as Ubuntu 20.04 v1.1.0, 5.3.10, use a FILE_CONTENT_CHECK that reads the configuration file text instead of the effective value. The item number, and in newer benchmarks the title, changes between benchmarks and versions, so search your report for PermitRootLogin or “root login” rather than a fixed number or title. Some examples:

Tenable audit (CIS benchmark) Item title
CIS Oracle Linux 7 Server L1 v3.0.0 5.2.10 Ensure SSH root login is disabled
CIS Debian Linux 11 Server L1 v1.0.0 5.2.7 Ensure SSH root login is disabled
CIS Ubuntu Linux 20.04 LTS Server L1 v1.1.0 5.3.10 Ensure SSH root login is disabled
CIS AlmaLinux OS 9 v2.0.0 L1 Workstation 5.1.20 Ensure sshd PermitRootLogin is disabled

These audit versions are only examples. Three of these four audits are deprecated on tenable.com: Oracle Linux 7 v3.0.0 (replaced by v3.1.1), Debian 11 v1.0.0 (replaced by v2.0.0) and Ubuntu 20.04 v1.1.0 (replaced by v2.0.1). The policy value in every version is no. Any other value fails: yes, prohibit-password, its older alias without-password, and forced-commands-only. The upstream OpenSSH default (since 7.0) is prohibit-password, and on RHEL/Oracle Linux 7 and 8 the shipped configuration effectively allows root password login. Either way, a host that never set the directive fails.

A different Nessus result sometimes appears next to it: plugin 85690, “OpenSSH 7.x < 7.1 PermitRootLogin Security Bypass”. That is a remote version check for an OpenSSH 7.0 logic error in prohibit-password handling. Its fix is upgrading OpenSSH, and editing sshd_config will not clear it.

Real-world risk, stated honestly

Allowing root over SSH is not a remotely exploitable flaw on its own. The CIS rationale is accountability: when admins log in with individual accounts and escalate with sudo, the logs show which person did what. When everyone logs in as root with a shared password or key, the auth log only says “root”.

The practical risks are:

  • Known target: every attacker already knows the account name, so password guessing only has to find one secret. prohibit-password disables password and keyboard-interactive authentication for root, which removes most of this, but still allows public key (and host-based or GSSAPI, if enabled) root logins.
  • One step to full control: a stolen root private key, or one copied into an automation server, gives full privileges directly with no sudo step and no individual attribution.
  • Audit findings: for regulated environments the failed control is itself the problem, whatever the technical exposure.

How to confirm it on the host

Check the effective value, not just the file. sshd -T must run as root. As an unprivileged user it fails with “Permission denied” on sshd_config or “no hostkeys available”.

# Effective value after all Include files are read
sudo sshd -T | grep -i '^permitrootlogin'

# Every file that sets it, plus Include and Match lines that change the order
sudo grep -RinEi '^s*(PermitRootLogin|Include|Match)' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/

# Effective value for a specific client, with Match blocks applied
# (host is the client's resolved hostname, not the server's)
sudo sshd -T -C user=root,host=admin-ws.example.com,addr=192.0.2.10 | grep -i '^permitrootlogin'

Plain sshd -T does not apply Match blocks. A Match Address block that sets PermitRootLogin yes for a management subnet only shows up when you pass -C with an address inside that range. Repeat the test for each Match criterion the grep found: addr= for Match Address, host= with a client hostname for Match Host, laddr= plus lport= with the server’s own IP and port for Match LocalAddress and LocalPort, and rdomain= for Match RDomain.

Before changing anything, find out who and what uses root over SSH today:

# Successful root logins in the last 30 days (unit is "ssh" on Debian/Ubuntu, "sshd" on RHEL family)
sudo journalctl -u ssh -u sshd --since "30 days ago" | grep 'Accepted .* for root '

# The same search in the syslog files (zgrep also reads rotated .gz files)
sudo zgrep -h 'Accepted .* for root ' /var/log/auth.log* /var/log/secure* 2>/dev/null

# Where sshd looks for keys and trusted certificate authorities
sudo sshd -T | grep -Ei '^(authorizedkeysfile|authorizedkeyscommand|trustedusercakeys)'

# Keys that allow root in today (check every file listed above)
sudo cat /root/.ssh/authorized_keys /root/.ssh/authorized_keys2 2>/dev/null

journalctl only reaches back to the last boot unless the journal is persistent (the /var/log/journal directory exists), which is why the syslog search is there too. The upstream default for AuthorizedKeysFile includes authorized_keys2 as well as authorized_keys. If authorizedkeyscommand or trustedusercakeys is not none, root may also get in through that command or a user certificate listing root as a principal.

Each key and each source IP in that output is a person, backup job, scanner or automation tool that will stop working when you apply the fix.

How to fix it

Step 1: make sure a named admin can get root through sudo

Debian and Ubuntu:

sudo adduser jdoe
sudo usermod -aG sudo jdoe

RHEL, Oracle Linux, AlmaLinux and Rocky Linux:

sudo useradd -m jdoe
sudo passwd jdoe
sudo usermod -aG wheel jdoe

Install the admin’s public key in ~jdoe/.ssh/authorized_keys. If sshd_config uses AllowUsers or AllowGroups, add the new account there too. Then, from a second terminal, prove it works end to end:

ssh jdoe@server
sudo -v && sudo whoami    # must print: root

Keep that session open until the change is verified.

Step 2: set PermitRootLogin no above any Include line

Recent Debian and Ubuntu packages ship an sshd_config with Include /etc/ssh/sshd_config.d/*.conf near the top, and RHEL 9 family systems use the same drop-in directory. Because the first value read wins, a line added at the bottom of the file is ignored if any drop-in sets the keyword. Back up the file, comment out existing PermitRootLogin lines (including any inside Match blocks and any written as PermitRootLogin=value), and insert the setting on line 1:

sudo cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo sed -i -E 's/^[[:space:]]*PermitRootLogin([[:space:]]|=).*/# &/I' /etc/ssh/sshd_config
sudo sed -i '1i PermitRootLogin no' /etc/ssh/sshd_config

The same commands work whether or not the file has an Include line, including on older releases such as RHEL 7 and Oracle Linux 7.

Step 3: clean up conflicting drop-in files

Your line now takes precedence, but leave nothing contradicting it. Back up the drop-in directory first, then edit or remove any file under /etc/ssh/sshd_config.d/ that the grep above showed setting PermitRootLogin to anything other than no.

sudo cp -a /etc/ssh/sshd_config.d /etc/ssh/sshd_config.d.bak

Also check for /etc/ssh/sshd_config.d/01-permitrootlogin.conf, which the RHEL 9 installer creates with PermitRootLogin yes when root password SSH login is enabled during installation. Remove it or set it to no. If configuration management owns the drop-in directory instead, put the setting in a file that sorts first, for example:

# /etc/ssh/sshd_config.d/00-permitrootlogin.conf
PermitRootLogin no

Drop-ins are read in lexical order, and the first value wins. Red Hat documents a two-digit prefix below 50 so an override is read before 50-redhat.conf, and a 00- prefix also sorts ahead of 01-permitrootlogin.conf. This only works if no PermitRootLogin line appears in sshd_config above the Include line.

Step 4: validate and reload

# Prints nothing and exits 0 when the configuration is valid
sudo sshd -t

# Debian / Ubuntu
sudo systemctl reload ssh

# RHEL, Oracle Linux, AlmaLinux, Rocky Linux
sudo systemctl reload sshd

A restart also works. Do it from the existing session and do not log out until Step 5 passes.

How to verify the fix and rescan

sudo sshd -T | grep -i '^permitrootlogin'
# expected: permitrootlogin no

# Reproduces the CIS audit command (which passes the server's own hostname)
sudo sshd -T -C user=root,host="$(hostname)",addr=192.0.2.10 | grep -i '^permitrootlogin'
# expected: permitrootlogin no (repeat with an address from each Match range)

# If the config has Match Host blocks, repeat for each client name pattern
sudo sshd -T -C user=root,host=admin-ws.example.com,addr=192.0.2.10 | grep -i '^permitrootlogin'

Then try it for real from another machine: ssh root@server should end in “Permission denied”, while ssh jdoe@server followed by sudo still works. This live test matters because sshd -T reads the files on disk. If you skip the reload, the audit can pass while the running daemon still accepts root.

Finally, rerun the same Nessus compliance scan with the same audit file. The item should change from FAILED to PASSED.

What can break and how to roll back

  • Automation that logs in as root: backup jobs, rsync or scp pulls, configuration management running with a root remote user, and cluster tooling. Move each one to a dedicated account with narrowly scoped sudo rules.
  • Your vulnerability scanner: if credentialed scans use root, they will start failing authentication. Our guide to setting up SSH credentials and sudo for Linux authenticated scans covers the replacement account.
  • Lockout: if no other account has working sudo, you lose remote admin access. The setting only affects SSH. Root can still log in on a local or out-of-band console if root has a password set. On Ubuntu and most cloud images root is locked, so plan recovery through single-user or rescue mode or the provider’s serial console.

To roll back, restore the main file, remove the drop-in you added, and restore any drop-ins you edited or removed:

sudo cp -a /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
sudo rm -f /etc/ssh/sshd_config.d/00-permitrootlogin.conf
sudo cp -a /etc/ssh/sshd_config.d.bak/. /etc/ssh/sshd_config.d/
sudo sshd -t && sudo systemctl reload sshd    # "ssh" on Debian/Ubuntu

If one job truly cannot move off root, forced-commands-only limits root to key logins that carry a forced command. It still fails the CIS item, so record it as a documented exception.

Common false positive reasons

  • The scan account could not run the check. sshd -T needs root. If sudo elevation is not configured for the compliance scan, the item can fail or error even on a correctly configured host.
  • The setting is correct only in a drop-in file. Depending on the audit version, the check may inspect the configuration file text instead of, or in addition to, the effective value. If sshd -T shows no and the item still fails, also set the directive in /etc/ssh/sshd_config above the Include line.
  • A Match block is overriding it. This is a true positive that looks false because plain sshd -T shows no. Test with -C as shown above.
  • It is a different finding. Plugin 85690 is an OpenSSH version check, not the CIS item, and only an upgrade clears it.

FAQ

PermitRootLogin prohibit-password vs no: which does CIS want?

CIS requires no. prohibit-password disables password and keyboard-interactive authentication for root but still allows public key (and host-based or GSSAPI, if enabled) root logins, so the audit reports it as a failure.

Why is PermitRootLogin no not working?

Usually because sshd read another value first: a file in /etc/ssh/sshd_config.d/ pulled in by an Include line above your edit, or a Match block for certain addresses. Other causes are forgetting to reload sshd, or editing /etc/ssh/ssh_config, which is the client configuration.

Does this stop su, sudo or console logins as root?

No. It only controls whether root can authenticate over SSH. Admins log in as themselves and use sudo or su as before.

Can I use sshd_config.d instead of editing the main file?

For sshd itself, yes, if the Include line comes before any PermitRootLogin line in sshd_config and your file sorts ahead of other drop-ins. Even though the Ubuntu 20.04 CIS text allows a drop-in, file-content audits may only pass when PermitRootLogin no is also in /etc/ssh/sshd_config, so the safest fix is the Step 2 edit.

Tracking this finding across many hosts

On a large Linux estate, the hard part is catching the hosts where a drop-in or Match block quietly reverses the setting. If you use SITEY, you can upload the .nessus export from the compliance scan, have its AI draft a host-specific script that runs only after a human approves it, deploy it through its agents on Linux endpoints, and then re-test to verify the finding is closed. Duplicates are merged per scanner, not across scanners.

Sources

SITEY closes the loop, not just the report.Discover, validate, fix and verify in your own infrastructure.

See pricing