You can audit a remote SSH server's configuration in one command:
ssh_scan -t ssh.example.comssh_scan is Mozilla's SSH configuration and policy scanner. It connects to a host, reads the algorithms the SSH daemon advertises in its key-exchange handshake (key exchange, host key, cipher, and MAC algorithms), and prints a JSON report of what it found and, by default, where that configuration fails Mozilla's "Modern" OpenSSH policy. It is a black-box check: it never logs in, it only inspects the negotiation the server is willing to do.
One thing up front, because it would be dishonest to leave it for the footnotes: Mozilla archived this project on 24 January 2022 and no longer maintains it. The last release was v0.0.44 in May 2021. The tool still runs and is still useful as a quick read of a server's offered algorithms, but the bundled policy is frozen at 2021's idea of "modern," and you should treat its pass/fail verdict as a starting point, not gospel. I cover what to use instead further down.
Install it
Two clean ways. The gem is the canonical install:
gem install ssh_scan
ssh_scan -t ssh.example.comIf you would rather not put an unmaintained Ruby gem on your machine, the Docker image keeps it contained:
docker run --rm -it mozilla/ssh_scan -t ssh.example.comBoth give you the same ssh_scan binary and the same flags. On a current Ruby (3.x) the gem can be fussy about its pinned dependencies; that is the cost of an archived project, and the Docker route sidesteps it.
Read the report
A scan against a host running an older OpenSSH looks like this. I have trimmed the long algorithm arrays for readability; the real output lists every offered algorithm in full:
{
"ssh_scan_version": "0.0.44",
"ip": "192.168.1.1",
"hostname": "",
"port": 22,
"server_banner": "SSH-2.0-OpenSSH_7.1p2 Debian-2",
"ssh_version": 2.0,
"os": "debian",
"ssh_lib": "openssh",
"key_algorithms": [
"curve25519-sha256@libssh.org",
"diffie-hellman-group14-sha1"
],
"encryption_algorithms_server_to_client": [
"chacha20-poly1305@openssh.com",
"aes256-gcm@openssh.com"
],
"mac_algorithms_server_to_client": [
"hmac-sha2-512-etm@openssh.com",
"hmac-sha1"
],
"compliance": {
"policy": "Mozilla Modern",
"compliant": false,
"recommendations": [
"Remove these Key Exchange Algos: diffie-hellman-group14-sha1",
"Remove these MAC Algos: umac-64-etm@openssh.com, hmac-sha1-etm@openssh.com, umac-64@openssh.com, hmac-sha1"
]
}
}The two halves to read: the algorithm arrays are the raw facts (this is exactly what the daemon offered), and the compliance block is ssh_scan's opinion about them. Here the server offers modern primitives (ChaCha20-Poly1305, AES-256-GCM, Curve25519) but also still advertises diffie-hellman-group14-sha1 and hmac-sha1, so it fails the Modern policy. The recommendations array is the actionable part: those are the lines you act on.
Note the recommendations only ever say "remove these." ssh_scan flags weak algorithms that should not be on the menu; it does not flag the absence of strong ones. That is a deliberate limitation worth knowing.
Translate a finding into an sshd_config change
The report above wants two things gone: a SHA-1 key-exchange algorithm and a SHA-1 MAC. You fix that by pinning explicit allow-lists in /etc/ssh/sshd_config, dropping the flagged entries:
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes256-ctr
Validate the syntax before you reload, so a typo does not lock you out of the box:
sudo sshd -t && sudo systemctl reload sshThe unit name differs by distro: it is ssh on Debian and Ubuntu (as in the example above), but sshd on RHEL, Fedora, and most others, so use sudo systemctl reload sshd there. Then re-run ssh_scan -t ssh.example.com and confirm "compliant": true. This edit-scan-reload loop is the whole point of the tool: it gives you a concrete list to act on instead of "your SSH is probably fine."
Useful flags
ssh_scan has a small, sane set of options. These are the ones I actually reach for:
| Flag | What it does |
|---|---|
-t, --target | The IP, hostname, or CIDR range to scan. Repeatable. |
-f, --file | Read targets from a file, one per line, for bulk scans. |
-p, --port | SSH port (default 22). Set this for daemons on a non-standard port. |
-P, --policy | Path to a custom policy file (YAML) instead of Mozilla Modern. |
-o, --output | Write the JSON report to a file instead of stdout. |
-T, --timeout | Per-connection timeout in seconds. |
--threads | Worker threads for multi-host scans (default 5). |
-V, --verbosity | Logging level (INFO, DEBUG, etc.). |
A bulk scan of every host in a file, written to a report, looks like:
ssh_scan -f hosts.txt -o ssh-audit-report.json --threads 10Writing your own policy
The default "Mozilla Modern" policy is just a YAML file, and you can supply your own with -P. A policy declares the algorithms you consider acceptable in each category; anything the server offers that is not on your list shows up in recommendations. A minimal custom policy:
name: "House SSH policy"
kex:
- curve25519-sha256@libssh.org
- diffie-hellman-group-exchange-sha256
macs:
- hmac-sha2-512-etm@openssh.com
- hmac-sha2-256-etm@openssh.com
encryption:
- chacha20-poly1305@openssh.com
- aes256-gcm@openssh.com
compression:
- noneRun it with ssh_scan -t ssh.example.com -P house-policy.yml. This is genuinely the most durable way to use the tool today: because the bundled Modern policy is frozen at 2021, maintaining your own short allow-list and scanning against that keeps the verdict meaningful even though the project is dead.
When not to reach for ssh_scan
Be honest about the archived status. For a one-off check of a server you can already log into, ssh -Q plus reading sshd -T output tells you what the daemon supports and what it is actually configured to negotiate, with no third-party tool at all:
ssh -Q kex # key-exchange algorithms this OpenSSH knows
sudo sshd -T | grep -iE 'kex|cipher|macs' # what THIS daemon is configured to offerFor an actively maintained external scanner, ssh-audit (jtesta's fork lineage, Python, still shipping releases) covers the same ground as ssh_scan and then some: it grades algorithms, flags known CVEs against the detected OpenSSH version, and notes missing strong algorithms, which ssh_scan never did. If I were standing this up fresh in 2026, that is what I would script into CI. ssh_scan remains handy because its JSON shape is simple and a lot of older audit pipelines already parse it, but I would not start a new project on it.
| Tool | Maintained | Best for |
|---|---|---|
ssh_scan | No (archived 2022) | Quick remote read of offered algorithms; legacy pipelines that already parse its JSON |
ssh-audit | Yes | Modern remote audits: grades, CVE flags, missing-strong-algorithm warnings |
ssh -Q + sshd -T | Yes (ships with OpenSSH) | Local checks on a box you already control, zero dependencies |
FAQ
See also
- Harden sshd: disable password authentication: the next step after fixing algorithms, moving the daemon to key-only logins so a guessed password can never get anyone in.
- Fix SSH "permissions are too open": the client-side counterpart, clearing the UNPROTECTED PRIVATE KEY FILE wall that blocks your key from being used at all.
Sources
Authoritative references this article was fact-checked against.
- mozilla/ssh_scan (GitHub, archived)github.com
- OpenSSH security guidelines (Mozilla)infosec.mozilla.org
- sshd_config(5) manual page (OpenBSD)man.openbsd.org





