Free tool · Runs in your browser

sshd_config Analyzer: Grade and Harden Your SSH Server Config

Paste your /etc/ssh/sshd_config and get a graded A to F review with a finding for every weak directive, the exact line it is on, and the line that fixes it. Handles Match blocks, Include drop-ins, and the first-value-wins rule that makes most SSH config checkers wrong. Nothing is uploaded.

Your config is analyzed entirely in this browser tab. Nothing is uploaded, logged, or sent anywhere. Never paste a private key into any website, including this one.

Try:

How to check your sshd_config

  1. On your server, print the config: cat /etc/ssh/sshd_config. Copy the output.
  2. Paste it into the box above. The review runs as you type, in your browser, and nothing is uploaded.
  3. Read the findings worst-first. Each one shows the directive, the line it is on, what it is set to now, and what it should be.
  4. If the tool warns that an Include may override your settings, run sudo sshd -T on the server and paste that output instead. It resolves every included file and Match block, so the answer is definitive.
  5. Switch to the Hardening patch tab, apply the safe lines, then verify with sudo sshd -t before reloading.

The first-value-wins rule that breaks most SSH config checkers

Almost every configuration format on a Linux server uses the last value it reads. sshd_config does the opposite.The manual is explicit: "unless noted otherwise, for each keyword, the first obtained value will be used".

So a file containing PasswordAuthentication yes on line 20 and PasswordAuthentication no on line 60 accepts passwords. sshd prints no warning, the second line is simply dead, and reading the file top to bottom gives you the wrong answer unless you know the rule. This tool flags every duplicate and tells you which line actually won.

A few directives are exceptions and accumulate instead of overriding: HostKey, ListenAddress, Port, AllowUsers, AllowGroups, AcceptEnv, Include and Subsystem. Reporting those as duplicates is a false positive, so this tool does not.

Why your Ubuntu server still accepts passwords

This is the most common way a hardened-looking config is not hardened at all. Ubuntu 22.04 and later ship an sshd_config whose first line is Include /etc/ssh/sshd_config.d/*.conf.

Combine that with first-value-wins and the consequence is severe: anything in that directory is read first and therefore overrides the whole main file. On a cloud image, cloud-init writes /etc/ssh/sshd_config.d/50-cloud-init.conf containing PasswordAuthentication yes. You can set PasswordAuthentication no in the main config, restart sshd, and still be accepting passwords.

When this tool sees an Includeahead of a directive it marks that finding "may be overridden" rather than quietly passing it. To settle it, run sudo sshd -T and paste the output: that is the fully resolved configuration sshd is actually running, with every include and default filled in.

Disabling passwords without locking yourself out

PasswordAuthentication nois the single most valuable line in an SSH config and the single easiest way to lose a server. If key login is not already working, applying it ends your access the moment sshd reloads, and the only way back is your provider's console or a rescue boot.

That is why the hardening patch here is split in two. Everything that cannot end your session is in one block. PasswordAuthentication, PermitRootLogin and the allow-list directives are in a separate block behind a warning, because each of them can.

The safe order is always the same: open a second terminal, log in with your key, confirm it does not ask for a password, and keep your first session open the entire time. Then apply, run sudo sshd -t to check the syntax, and sudo systemctl reload ssh rather than restart so existing sessions survive.

One more trap: PasswordAuthentication no on its own does not disable password login. PAM can still prompt through keyboard-interactive, so you need KbdInteractiveAuthentication no as well. The patch includes it. The exception is a server using PAM-based MFA such as Google Authenticator, where that line would lock you out instead.

Match blocks, and the exception you forgot about

A Match block applies its directives only to connections meeting a condition, and everything after it stays inside that block until the next Match or the end of the file. That makes it easy to write a global policy and then quietly undo it:

PermitRootLogin no at the top, then Match User deploy followed by PermitRootLogin yes three hundred lines down. The global setting looks right in a review, and root login is still possible.

This tool evaluates Match blocks separately from global scope. A weak directive inside a Match block is never reported as your global setting, and a Match block that re-enables something the global scope disabled gets its own finding.

What a config file cannot tell you

This tool reads text. It is honest about the limits of that. It cannot tell you whether a key in authorized_keys still belongs to someone who works here, whether an account has a blank password, who is in the sudo group, or whether an old employee still has access to three of your servers. None of that is in sshd_config.

Those checks need the running machine. The SSH and Access checklist lists all eleven of them as plain shell commands you can paste into a terminal, and the fix pages cover what to do with each result. CtrlOps runs the whole set over SSH across every server you manage and hands back a hardening score and a PDF, but the checklist works perfectly well on its own.

The directives that matter most, and what they should be

DirectiveDefault if unsetWhat you wantWhy
PermitRootLoginprohibit-passwordnoThe default still lets root in with a key. Use a normal account and sudo so actions are attributable.
PasswordAuthenticationyesnoThe default is the insecure value. Leaving it alone means your server accepts passwords.
KbdInteractiveAuthenticationyesnoPAM can still prompt for a password through this even after PasswordAuthentication no.
PermitEmptyPasswordsnonoSet to yes, any account with a blank password can log in over the network.
MaxAuthTries63Each connection gets this many guesses before it is dropped.
AllowGroupsunsetssh-usersWithout an allow list, every account on the host may attempt SSH.
ClientAliveInterval0300Zero means an abandoned session stays open indefinitely.
LogLevelINFOVERBOSEVERBOSE records the key fingerprint used for each login, which is what makes an audit possible.
X11Forwardingno (yes on Ubuntu)noDebian and Ubuntu ship yes. On a headless server it is unused attack surface.
sshd_config FAQ

Frequently asked questions

Yes. The analysis runs entirely in your browser using JavaScript on this page. Your config is never uploaded, logged, or transmitted anywhere, and there is no server to store it. You can confirm this by opening your browser devtools network tab while the tool runs. That said, never paste a private key into any website. This tool refuses input that looks like one.
It is a deliberate design choice documented in sshd_config(5): "unless noted otherwise, for each keyword, the first obtained value will be used." It exists so an Include at the top of the file can override defaults set further down. It surprises almost everyone, because nearly every other config format on Linux uses the last value. A duplicated directive means the later line does nothing, and sshd prints no warning about it.
Two common causes. First, on Ubuntu 22.04 and later the main config starts with Include /etc/ssh/sshd_config.d/*.conf, and because sshd uses the first value it finds, a file in that directory overrides yours. Cloud images ship 50-cloud-init.conf with PasswordAuthentication yes. Second, PasswordAuthentication no does not close the PAM keyboard-interactive path, so you also need KbdInteractiveAuthentication no. Run sudo sshd -T to see which value is actually in effect.
sshd_config is the file you edit. sshd -T prints the fully resolved configuration sshd is actually running, with every Include followed, every default filled in, and Match blocks evaluated for the given connection. Pasting the file gives you line numbers and catches duplicates and dead lines. Pasting sshd -T output gives you a definitive answer with no caveats. Use both: the file to find mistakes, the -T output to confirm the result.
It can, which is why the patch is split. Lines that cannot end your session are in one block. PasswordAuthentication, PermitRootLogin, KbdInteractiveAuthentication and the allow-list directives are in a separate block behind a warning, because applying them without working key authentication locks you out permanently. Always open a second terminal, confirm key login works there, keep your original session open, then run sudo sshd -t before reloading.
Because moving off port 22 is obscurity, not security. It reduces log noise from automated scanners and nothing else, a real attacker finds the new port in seconds, and changing the port without opening it in the firewall first is a common way to lose access. The tool notes that you are on port 22 so you know why your logs are full, but it deliberately keeps that line out of the patch. Key-only authentication and Fail2ban are what actually help.
No, and this is where most manual reviews go wrong. An absent directive runs OpenSSH's compiled-in default, and several of those defaults are the insecure value. PasswordAuthentication defaults to yes. PermitRootLogin defaults to prohibit-password, which still allows root in with a key. Some defaults also vary by distribution: X11Forwarding is no upstream but yes on Debian and Ubuntu. This tool marks findings that come from a default so you can tell the two cases apart.
No. It reviews one file. It cannot see who can actually log in, which keys are in authorized_keys, whether an account has a blank password, or who has sudo, because none of that lives in sshd_config. The SSH and Access checklist covers all eleven server-side checks as shell commands you can run yourself, and CtrlOps automates them across a fleet.
Current OpenSSH, and the algorithm suggestions follow the Mozilla OpenSSH server guidelines. The tool also flags directives that no longer exist: Protocol and UsePrivilegeSeparation were removed in OpenSSH 7.4 and 7.5, ChallengeResponseAuthentication was renamed to KbdInteractiveAuthentication in 8.7, and DSA host keys were disabled by default in 9.8. A config still carrying those has not been reviewed in a long time.
A Match block applies only to connections meeting its condition, and it scopes every directive after it until the next Match or the end of the file. A weak setting inside Match User deploy is not your global policy, so reporting it as one would be a false positive. This tool evaluates global scope and Match scope separately, and raises a distinct finding when a Match block re-enables something the global config disabled.
One config is easy. Twenty is the problem.

Check one file here, audit every server there.

CtrlOps runs 25 security audits over SSH across your whole fleet, scores each server, and fixes findings with approval-gated AI. Your keys never leave your machine.

Start instantly· No credit card· No sneaky autorenewals