An SSH security audit reviews your server's SSH daemon configuration, user permissions and authentication rules to find the gaps an attacker looks for first. This page documents the 11 checks in the CtrlOps SSH & Access audit, in the order the audit runs them.
Every threshold below is transcribed from the audit script itself, not from general advice. Where the script passes a value, this page says it passes; where the script warns rather than fails, this page says so and explains why the distinction matters.
Key takeaways
Eleven settings separate a hardened SSH configuration from an exposed one. Each takes a minute or two to check by hand, which is fine for one server and stops being fine somewhere around the fifth.
- Four checks are rated HIGH: root login, authorized keys, extra UID 0 accounts and login restrictions. If you only have ten minutes, do those four.
- Two need root: reading other accounts'
authorized_keysfiles and reading/etc/sudoers. The other nine run as any user. - The whole audit takes about 14 seconds when CtrlOps runs it over your existing SSH connection, against roughly 11 minutes by hand.
- The real problem is drift, not the first audit. A setting relaxed "just for testing" in March is still relaxed in September unless something re-checks it.
| # | Check | Severity | What it catches | Root needed |
|---|---|---|---|---|
| 1 | Root login | HIGH | Direct root access over SSH | No |
| 2 | Password auth | MEDIUM | Brute-forceable password logins | No |
| 3 | SSH port | LOW | Scanner noise on port 22 | No |
| 4 | Idle timeout | LOW | Sessions that never expire | No |
| 5 | Authorized keys | HIGH | Unknown or writable key files | Yes |
| 6 | Extra UID 0 accounts | HIGH | Hidden superuser accounts | No |
| 7 | Sudo group members | MEDIUM | Over-privileged accounts | No |
| 8 | Passwordless sudo | MEDIUM | Unrestricted NOPASSWD grants | Yes |
| 9 | Login restrictions | HIGH | Empty passwords, no allow-list | No |
| 10 | Crypto algorithms | MEDIUM | SHA-1 key exchange, CBC ciphers | No |
| 11 | Password ageing | LOW | Passwords that never expire | No |
/etc/ssh/sshd_config and follow any Include drop-ins. Match blocks are not evaluated, so a setting that is overridden only inside a Match block will not be reflected here. When in doubt, confirm with sudo sshd -T on the host.Check 1: Is root login over SSH disabled?
Allowing direct root login means an attacker needs to guess one credential for the most powerful account on the system. Disabling it forces two separate steps: authenticate as an ordinary user, then escalate with sudo. That second step is also where your audit trail comes from, because sudo logs who ran what while a direct root session does not.
How to check manually
grep -i "^PermitRootLogin" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null| Result | When | What it means |
|---|---|---|
| PASS | The effective value is no | Root login is disabled entirely. |
| WARN | The value is prohibit-password or without-password | Root can still log in with a key. Better than a password, but you lose the sudo audit trail. |
| FAIL | Any other value, including yes | Root login is allowed. Set PermitRootLogin no. |
| SKIP | /etc/ssh/sshd_config is not readable | The audit could not read the file. Check the path and its permissions. |
No output at all does not mean you are safe. It means no explicit directive exists and you are inheriting the package default, which on most modern distributions is prohibit-password - the warning case, not the passing one.
How to fix it
# Back up first
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# Set this line in /etc/ssh/sshd_config
PermitRootLogin no
# Validate the syntax before restarting
sudo sshd -t
sudo systemctl restart sshdsudo and a tested SSH key. Locking out root without a second path in means losing the server. sudo sshd -t catches syntax errors before they take down your only way back.Check 2: Is SSH password authentication off?
Passwords are the single most automated attack surface on an internet-facing server. Microsoft's 2025 Digital Defense Report attributes 97% of identity attacks to password spray, which is exactly this: automated tools throwing common passwords at every account name they can find. SSH keys remove the entire category, because there is nothing guessable to spray.
This check is rated MEDIUM rather than HIGH for one reason: turning it off wrongly locks people out faster than any other setting on this list.
How to check manually
grep -i "^PasswordAuthentication" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null| Result | When | What it means |
|---|---|---|
| PASS | The effective value is no | Key-based authentication only. |
| FAIL | Any other value, or no directive at all | Password auth is enabled. Test key login first, then set PasswordAuthentication no. |
| SKIP | /etc/ssh/sshd_config is not readable | The audit could not read the file. |
How to fix it
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# In /etc/ssh/sshd_config
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
sudo sshd -t
sudo systemctl restart sshdKeep UsePAM yes. PAM is still what carries MFA, account policy, OS Login and cloud IAM integration even when passwords are off.
KbdInteractiveAuthentication only exists on OpenSSH 8.7 and later (2021 onward). On CentOS 7 and older LTS releases, sshd -t will reject it as an unsupported option. Check with sshd -V first and use ChallengeResponseAuthentication no on older builds.Check 3: Is SSH running on a non-default port?
This is the one check on the list that is explicitly not about security. A port scan finds SSH on any port in seconds, so moving off 22 stops exactly zero targeted attacks. What it does is drop the automated background noise from thousands of attempts a day to nearly none, which makes a real intrusion attempt visible in your auth log instead of buried in it.
That is why the audit warns rather than fails here. Port 22 with Fail2Ban and key-only auth is a perfectly defensible configuration.
How to check manually
grep -i "^Port" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null| Result | When | What it means |
|---|---|---|
| PASS | A non-default port is configured | Automated scanners will mostly miss this host. |
| WARN | Port 22, or no directive at all | Expect brute-force noise in the logs. Keep Fail2Ban active. |
| SKIP | /etc/ssh/sshd_config is not readable | The audit could not read the file. |
How to fix it
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# In /etc/ssh/sshd_config, pick a high port
Port 2222
# Open the new port BEFORE restarting sshd
sudo ufw allow 2222/tcp
sudo sshd -t
sudo systemctl restart sshdCheck 4: Do idle SSH sessions time out?
An SSH session left open on an unlocked laptop is an authenticated session anyone with physical access inherits. ClientAliveInterval tells sshd how often to probe a quiet client, and ClientAliveCountMax how many unanswered probes to tolerate before hanging up.
The recommended pair is 300 and 2, which drops a dead session after 600 seconds.
How to check manually
grep -i "^ClientAlive" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null| Result | When | What it means |
|---|---|---|
| PASS | ClientAliveInterval is set to a non-zero value | Idle sessions drop after interval x count-max seconds. |
| WARN | ClientAliveInterval 0, or no directive at all | No idle timeout. Set ClientAliveInterval 300 and ClientAliveCountMax 2. |
| SKIP | /etc/ssh/sshd_config is not readable | The audit could not read the file. |
How to fix it
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# In /etc/ssh/sshd_config
ClientAliveInterval 300
ClientAliveCountMax 2
sudo sshd -t
sudo systemctl restart sshdWith 300 and 2, sshd sends a probe after five minutes of silence, another five minutes later, and closes the session at the ten-minute mark if neither is answered.
Check 5: Which SSH keys can log in?
Every line in an authorized_keys file is a working credential that never expires and never prompts. They accumulate quietly: a contractor who finished last year, a CI pipeline that was rebuilt, a laptop that was replaced. Nothing on the server ever removes them.
The permissions half matters more than the count. A authorized_keys file that is writable by group or other means any user on that host can append their own public key and log in as that account whenever they like.
getent and not /home/*. The audit enumerates accounts with UID 0 or UID 1000 and above via getent passwd rather than globbing /home. Service accounts and admin accounts frequently have home directories outside /home, and a glob silently misses every one of them.How to check manually
# Enumerate real account homes, count keys, read permissions
getent passwd | awk -F: '($3 == 0 || $3 >= 1000) {print $6}' | sort -u | while read -r home; do
keyfile="$home/.ssh/authorized_keys"
[ -f "$keyfile" ] || continue
count=$(grep -cE '(ssh-(rsa|dss|ed25519)|ecdsa-sha2|sk-(ssh|ecdsa))' "$keyfile" 2>/dev/null)
perms=$(stat -c '%a' "$keyfile" 2>/dev/null || stat -f '%Lp' "$keyfile" 2>/dev/null)
echo "$keyfile: $count key(s), permissions: $perms"
done| Result | When | What it means |
|---|---|---|
| PASS | Key files were found and none are group- or world-writable | Review the reported key count per account and remove any key you do not recognize. |
| WARN | No readable authorized_keys file was found anywhere | Expected only if this host uses another authentication method. Otherwise the audit could not see the files. |
| FAIL | Any authorized_keys file is writable by group or other | Another user on this host can append their own key. Run chmod 600 on each file. |
How to fix it
# Lock down permissions
chmod 600 /home/username/.ssh/authorized_keys
chmod 700 /home/username/.ssh
# Then remove keys you do not recognize
nano /home/username/.ssh/authorized_keysauthorized_keys entry is a working credential for as long as the host exists. SSH key management best practices covers the rotation and naming side of this.Check 6: Does any account other than root have UID 0?
Linux grants superuser rights by UID, not by name. An account called backup-svc with UID 0 has exactly the same power as root while looking unremarkable in a user list. Creating one is a long-standing persistence technique after a compromise, precisely because it survives a password reset on the real root account.
The audit uses getent rather than reading /etc/passwd directly so that LDAP and SSSD-backed accounts are covered too. On a domain-joined host, reading the local file alone would miss them entirely.
How to check manually
# Covers local, LDAP and SSSD-backed accounts
getent passwd | awk -F: '$3 == 0 && $1 != "root" {print $1}'| Result | When | What it means |
|---|---|---|
| PASS | No output | Only root holds UID 0. |
| FAIL | One or more account names are printed | Each one is a full superuser account. Investigate immediately. |
How to fix it
Do not delete the account first. Establish how it got there:
passwd -S suspicious_account # account status and password age
last suspicious_account # login history
ps -u suspicious_account # anything running as it right nowThen remove it once you understand the answer:
sudo userdel suspicious_accountCheck 7: Who is in the sudo group?
Sudo membership grows and never shrinks. Someone is added during an incident at 2am, the incident ends, and nobody removes them. Twelve months later that list is the real answer to "who can take this server down", and it rarely matches anyone's mental model.
The part people miss is primary-group membership. getent group sudo lists supplementary members only. An account created with sudo as its primary group never appears in that output and is invisible to the obvious one-liner.
How to check manually
# Supplementary members
getent group sudo wheel admin 2>/dev/null
# Primary-group members, which the line above does not show
SGIDS=$(getent group sudo wheel admin 2>/dev/null | cut -d: -f3 | tr '\n' '|' | sed 's/|$//')
getent passwd | awk -F: -v g="^($SGIDS)$" '$4 ~ g {print $1}'| Result | When | What it means |
|---|---|---|
| PASS | One or more accounts are in sudo, wheel or admin | The privileged accounts are listed. Confirm each still needs access. |
| WARN | No members in any of the three groups | Nobody can escalate. Make sure you are not relying on direct root login instead. |
A pass here is not automatically good news. The audit can list the accounts, but only you know whether deploy and backup should be able to run anything as root, or whether they should have scoped rules instead.
How to fix it
sudo deluser username sudo # Debian, Ubuntu
sudo gpasswd -d username wheel # RHEL, Rocky, AlmaCheck 8: Which sudo rules skip the password?
Scoped NOPASSWD is a legitimate pattern. A deployment user that needs systemctl restart myapp without an interactive prompt is a reasonable thing to configure, and the audit does not fail it.
Blanket NOPASSWD: ALL is different in kind, not degree. It means that whoever compromises that account, through a stolen key or a vulnerable web app running as that user, gets instant unrestricted root with nothing left to defeat.
How to check manually
sudo grep -rhE '^[^#]*NOPASSWD' /etc/sudoers /etc/sudoers.d/ 2>/dev/null| Result | When | What it means |
|---|---|---|
| PASS | No NOPASSWD rules exist | Every sudo invocation requires a password. |
| WARN | NOPASSWD rules exist, all scoped to specific commands | Confirm none of the named commands can be used to spawn a shell. |
| FAIL | One or more rules grant NOPASSWD: ALL | That account is unrestricted root with no second factor. Scope it to the commands it actually needs. |
| SKIP | The audit could not read /etc/sudoers | This check needs root. Run the audit with sudo. |
How to fix it
# visudo validates syntax before saving - never edit sudoers with a plain editor
sudo visudo
# Replace: deploy ALL=(ALL) NOPASSWD: ALL
# With: deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myappNOPASSWD rule for /usr/bin/vim lets that user open a root shell from inside vim in two keystrokes. The same is true of less, find, awk and anything else with a shell escape. Restrict the list to non-interactive commands.Check 9: Are SSH logins restricted?
These three settings are evaluated as one check because they describe the same thing from different angles: who is permitted to attempt an SSH login, and how many attempts each connection gets.
The allow-list is the one most servers are missing. Without AllowUsers or AllowGroups, every account that exists on the host, including service accounts that were never meant to be interactive, is a valid login target.
How to check manually
grep -iE "^(PermitEmptyPasswords|MaxAuthTries|AllowUsers|AllowGroups)" \
/etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null| Result | When | What it means |
|---|---|---|
| PASS | Empty passwords refused, MaxAuthTries 6 or lower, and an allow-list is set | Access is limited to the named accounts or groups. |
| FAIL | PermitEmptyPasswords yes | Any account with a blank password can log in over the network. Set it to no. |
| WARN | MaxAuthTries is greater than 6 | Each connection gets that many guesses. Lower it to 3. |
| WARN | No AllowUsers and no AllowGroups | Every account on the host may attempt SSH. Restrict it to the accounts that need it. |
| SKIP | /etc/ssh/sshd_config is not readable | The audit could not read the file. |
Two details worth knowing. The OpenSSH default for MaxAuthTries is 6, and the audit only warns above that, so an unconfigured host passes this sub-check. And the conditions are evaluated in order, so a host with PermitEmptyPasswords yes reports that failure and stops - fix it and re-run to see what else is waiting.
How to fix it
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# In /etc/ssh/sshd_config
PermitEmptyPasswords no
MaxAuthTries 3
AllowUsers your_admin_user your_deploy_user
sudo sshd -t
sudo systemctl restart sshdAllowUsers is exclusive. Once it exists, accounts not on the list cannot SSH in, including yours if you mistype it. Run sudo sshd -t, then open a second session and confirm it works before you close the one you are in.Check 10: Are weak SSH ciphers still accepted?
CBC-mode ciphers have known plaintext-recovery attacks against SSH specifically, and SHA-1 collisions are practical rather than theoretical. Neither belongs in a 2026 configuration.
The warning case is the one that catches people. Leaving all three directives unset feels neutral, but it means the effective algorithm list is whatever your distribution shipped. On older releases that list still enables SHA-1 key exchange and CBC ciphers, so "unconfigured" and "insecure" are frequently the same state.
How to check manually
grep -iE "^(KexAlgorithms|Ciphers|MACs)" \
/etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null| Result | When | What it means |
|---|---|---|
| PASS | Algorithms are pinned and none are weak | No SHA-1 key exchange, CBC ciphers or weak MACs are configured. |
| FAIL | A weak entry appears in any of the three lists | SHA-1 key exchange and CBC ciphers are downgrade and plaintext-recovery material. Pin modern lists. |
| WARN | None of KexAlgorithms, Ciphers or MACs is set | You inherit the package default, which on older distributions still enables SHA-1 and CBC. |
| SKIP | /etc/ssh/sshd_config is not readable | The audit could not read the file. |
Specifically, the audit flags diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 and diffie-hellman-group-exchange-sha1 in KexAlgorithms; 3des-cbc, arcfour, blowfish-cbc, cast128-cbc and any aes*-cbc in Ciphers; and hmac-md5, hmac-sha1, hmac-sha1-96 and umac-64@ in MACs.
How to fix it
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
# In /etc/ssh/sshd_config
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
sudo sshd -t
sudo systemctl restart sshddiffie-hellman-group14-sha256 as a fallback, never a SHA-1 variant.Check 11: Do passwords ever expire?
Even with SSH password authentication disabled, /etc/login.defs still governs local console login and any PAM-backed service on the host. A password that never expires stays valid for as long as the account does, which means a credential leaked two years ago still works.
The important caveat is scope: PASS_MAX_DAYS binds new accounts only. Changing it does nothing to accounts that already exist.
How to check manually
grep -E "^PASS_MAX_DAYS|^PASS_MIN_LEN" /etc/login.defs 2>/dev/null| Result | When | What it means |
|---|---|---|
| PASS | PASS_MAX_DAYS is 365 or lower | An expiry policy exists. Note it binds new accounts only - chage -l confirms existing ones. |
| WARN | PASS_MAX_DAYS is greater than 365 | Effectively no expiry. Set 90 if this host uses password auth. |
| WARN | No PASS_MAX_DAYS line exists | Passwords never expire. |
| SKIP | /etc/login.defs is not readable | The audit could not read the file. |
How to fix it
# For new accounts, in /etc/login.defs
PASS_MAX_DAYS 90
# Existing accounts have to be updated individually
sudo chage -l username # view the current policy
sudo chage -M 90 username # set a 90-day maximumWhat this audit does not cover
Being explicit about scope is part of the point of publishing thresholds. This audit reads configuration; it does not test behaviour from the outside.
Matchblocks are not evaluated. Values are parsed fromsshd_configand itsIncludedrop-ins. A directive overridden inside aMatch UserorMatch Addressblock will not be reflected. Confirm withsudo sshd -Twhen a host uses them heavily.- No live handshake. Check 10 reads the configured algorithm lists rather than negotiating a connection, so it reports what sshd is told to offer, not what a specific client ends up using.
- No brute-force protection check. Fail2Ban, sshguard and CrowdSec are not in this audit. Firewall and network exposure are covered by the Firewall & Network audit instead.
- Key strength is not evaluated. Check 5 counts keys and reads file permissions. It does not judge whether a given key is a 1024-bit RSA key that should have been retired.
The manual audit problem at scale
Running these 11 checks on one server takes about 11 minutes, including reading each output and deciding whether it is a pass, a warning or a failure. For one server that is a reasonable afternoon.
Then multiply it. A freelancer with 8 client servers: 88 minutes. An agency running 20 staging and production environments: over 3 hours. A startup with 12 servers across development, staging and production: more than 2 hours, and this is not a once-a-year job.
Here is what the manual version actually looks like:
- SSH into server #1
- Run 11 commands, one at a time
- Read each output and interpret whether it passed, warned or failed
- Write the findings down somewhere
- SSH into server #2
- Repeat steps 2 to 4
- Continue for every server in the fleet
- Compare the results to work out which servers need attention first
- Go back to the worst ones and start fixing
No score. No report you can hand a client. No record of what the state was last time, so no way to prove a fix actually improved anything. Just terminal scrollback and a spreadsheet you will forget to update.
The real cost is not the time, it is the drift. You audit thoroughly in January and fix everything. By March someone has re-enabled password auth on a staging box because a deployment script needed it, and nobody wrote that down. Without something that re-checks, every fix has a half-life. That is the same failure mode as managing multiple servers without a central view.
How CtrlOps runs all 11 checks in one click
Instead of running 11 commands on every server by hand, CtrlOps runs the whole SSH & Access audit over the SSH connection you already have open. Nothing is installed on the server: no agent, no daemon, no package, no new credentials. The checks are read-only POSIX shell that executes, streams its results back, and leaves nothing behind.
Watch it run
Two and a half minutes, start to finish: selecting the audit, watching the checks stream in, reading the score, and sending the failures to the AI Terminal for a fix. The written steps below cover the same ground if you would rather read than watch.
Step 1: Open the Audit tab
Connect to your server in CtrlOps and open the Audit tab in the left sidebar.
Step 2: Select SSH & Access
Choose the SSH & Access audit from the catalog. All 11 checks are listed with their descriptions, severity levels and the estimated run time (about 14 seconds in total). Toggle off any check you want to skip.
Step 3: Run it
The audit runs over your existing SSH session. Results stream in as they complete, so you watch each check pass, warn or fail rather than waiting for a final report.
Step 4: Read the report
You get a summary containing:
- A hardening score out of 100
- A severity breakdown: how many HIGH, MEDIUM and LOW findings
- Pass, warning, failed and skipped counts
- A findings table you can sort and filter
Step 5: Fix what failed
Select the failed findings and send them to the AI Terminal. Each proposed fix command appears with an explanation of what it changes. Nothing runs until you review and approve it.
Step 6: Re-run and compare
Run the same audit again after applying fixes and watch the hardening score move. That delta is the thing a spreadsheet never gave you: evidence that the fix landed.
The whole cycle, audit then fix then verify, happens in one window. No switching terminals, no copy-pasting commands out of a checklist, no scoring by hand.
For fleet-wide work, the Audit Reports view on the Home screen runs the same audit across every connected server at once. Each server gets its own report, and the hardening scores line up side by side so you can see which box to open first.
| Task | By hand, 8 servers | CtrlOps, 8 servers |
|---|---|---|
| Run all 11 checks | About 88 minutes | About 2 minutes |
| Get a scored report | Not available | Automatic |
| Export a PDF for a client | Not available | One click |
| Fix what failed | Copy-paste from your notes | AI-generated commands behind an approval gate |
| Re-audit after fixing | Another 88 minutes | Another 2 minutes |
Conclusion
For a proper security audit, CtrlOps is the best option. While manual checks using commands are helpful for single servers, managing configurations at scale requires a centralized, automated approach.
The CtrlOps Security Audit feature handles all 11 SSH checks and more across your entire fleet in one click. It gives you a clear hardening score, client-ready PDF reports, and AI-assisted commands to fix vulnerabilities instantly. Use CtrlOps to eliminate configuration drift and keep your servers secure.
Frequently asked questions
An SSH security audit checklist is a structured set of configuration checks run against a server's SSH daemon and access controls. It covers root login permissions, password authentication, the listening port, idle timeouts, authorized keys, accounts holding UID 0, sudo group membership, passwordless sudo rules, login restrictions, cryptographic algorithms and password ageing. The goal is to find misconfigurations before someone else does.
Run one after every change to who has access: a new hire, a departure, a contractor finishing, or a server being provisioned. Beyond that, monthly is a reasonable baseline for production. The reason is configuration drift rather than new vulnerabilities: settings relaxed for a quick test are rarely reverted, and nothing on the server reminds you.
PermitRootLogin no blocks all root login over SSH, both passwords and keys. prohibit-password (previously without-password) blocks password-based root login but still allows key authentication as root. The audit passes no and warns on prohibit-password, because key-based root login still bypasses the sudo audit trail that records who ran which command.
It reduces automated brute-force noise substantially but adds no meaningful protection against a targeted attacker, who will find the new port with a scan in seconds. That is why this check warns rather than fails. Treat it as log hygiene that makes real intrusion attempts visible, not as a security control, and never as a substitute for key-only authentication.
It breaks any workflow that types a password to connect, which in practice means some CI/CD pipelines, older deployment scripts and SFTP clients configured with passwords. Before disabling it, confirm every human and service account that connects has a working key, and test key login in a separate terminal while your current session is still open.
It reads ~/.ssh/authorized_keys in every account's home directory, and those files are readable only by their owner and root. Without root the audit can see your own file and nothing else, which would report a clean result on a server that has unknown keys sitting in another account. The check is skipped rather than guessed when it cannot read them.
It means that account can run any command as root with no password prompt. If an attacker gets that account, through a stolen key, a vulnerable application running as that user, or a hijacked session, they have unrestricted root immediately and nothing else to defeat. Scoped NOPASSWD rules naming specific non-interactive commands are a different matter and only draw a warning.
Use curve25519-sha256 for key exchange, chacha20-poly1305@openssh.com or aes256-gcm@openssh.com for ciphers, and hmac-sha2-512-etm@openssh.com for MACs. Remove all SHA-1 key exchange, every CBC-mode cipher, RC4 variants and MD5 or SHA-1 MACs. Test each client that connects to the server before rolling the change out, because clients older than about 2019 may not negotiate these.
Yes. Every check works on any Linux host reachable over SSH: EC2, DigitalOcean Droplets, Linode, Vultr, Hetzner, bare metal or a VPS from any other provider. The audit scripts detect the distribution family (Debian, Ubuntu, RHEL, Rocky, Alma, Amazon Linux, Fedora, Oracle, SUSE, Alpine) and adapt the commands accordingly.
If your workloads run on Kubernetes, serverless platforms such as Lambda or Cloud Functions, or a managed PaaS where you do not control the SSH layer, this audit does not apply: those platforms handle access differently and you have no sshd to configure. It is for servers you access and administer over SSH.
No. The 11 checks are read-only POSIX shell scripts executed over the SSH session you already have open. Nothing is installed, no agent or daemon runs afterwards, and nothing is written to the target host. Two of the checks need root to read other accounts' files, and are reported as skipped rather than guessed if you run without it.
ssh-audit connects from outside and evaluates the algorithms a server actually negotiates, which is a genuinely different angle from reading the config. Lynis is installed on the host and covers far more than SSH. This audit runs agentlessly over an existing connection, publishes the exact thresholds it applies, and feeds failed findings into a guided fix step. They complement each other rather than replacing one another.