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 (both live in the SSH cheat sheet alongside the rest of the everyday commands):
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
No. It connects only far enough to complete the algorithm-negotiation phase of the SSH handshake, reads the key-exchange, host-key, cipher and MAC algorithms the daemon advertises, then disconnects. It never authenticates, so it needs no credentials and leaves no login in the auth log (though the connection attempt itself may be logged).
No. Mozilla archived the ssh_scan repository on 24 January 2022, and the last release was v0.0.44 in May 2021. It still runs, but its bundled "Mozilla Modern" policy is frozen at 2021. For a maintained alternative, use ssh-audit, or check a server you control locally with ssh -Q and sshd -T.
Use ssh-audit for anything new. Both scan a remote SSH daemon and report the offered algorithms, but ssh-audit is actively maintained, grades each algorithm, flags known CVEs against the detected OpenSSH version, and warns when strong algorithms are missing, none of which ssh_scan does. Reach for ssh_scan only when an existing pipeline already parses its specific JSON shape, or when you want a quick read against a custom YAML policy.
It means the server offers at least one algorithm that the policy (Mozilla Modern by default) considers too weak. The recommendations array lists exactly which key-exchange, cipher, MAC or compression algorithms to remove. It does not check whether strong algorithms are present, only that weak ones are absent, so a passing scan is a floor, not proof of a hardened config.
Yes. Pass a YAML policy file with -P, for example ssh_scan -t host -P house-policy.yml. The file lists the acceptable algorithms per category; anything offered that is not on your list lands in the recommendations. Maintaining your own short allow-list is the most reliable way to keep the verdict meaningful now that the bundled policy is no longer updated.
Set explicit KexAlgorithms, Ciphers and MACs allow-lists in /etc/ssh/sshd_config that exclude the flagged entries, validate with sudo sshd -t, then reload sshd and re-scan. Pin only the strong algorithms you want negotiated rather than trying to blocklist the weak ones one at a time.
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.
- SSH cheat sheet: the everyday commands, including the
ssh -Qandsshd -Tchecks used above for a zero-dependency local audit. - Fix "Host Key Verification Failed": what to do when a host key changes under you, the other end of the same key-exchange handshake ssh_scan inspects.
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





