TechEarl

ssh_scan: Audit Your SSH Server Configuration

ssh_scan is Mozilla's SSH configuration and policy scanner: point it at a host, get a JSON report of the algorithms the daemon offers and where they fail a security policy.

Ishan Karunaratne⏱️ 8 min readUpdated
Share thisCopied
Use ssh_scan to audit an SSH server's key exchange, cipher and MAC algorithms against the Mozilla Modern policy and get a JSON compliance report. Note: the project is archived.

You can audit a remote SSH server's configuration in one command:

bash
ssh_scan -t ssh.example.com

ssh_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:

bash
gem install ssh_scan
ssh_scan -t ssh.example.com

If you would rather not put an unmaintained Ruby gem on your machine, the Docker image keeps it contained:

bash
docker run --rm -it mozilla/ssh_scan -t ssh.example.com

Both 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:

json
{
  "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:

code
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:

bash
sudo sshd -t && sudo systemctl reload ssh

The 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:

FlagWhat it does
-t, --targetThe IP, hostname, or CIDR range to scan. Repeatable.
-f, --fileRead targets from a file, one per line, for bulk scans.
-p, --portSSH port (default 22). Set this for daemons on a non-standard port.
-P, --policyPath to a custom policy file (YAML) instead of Mozilla Modern.
-o, --outputWrite the JSON report to a file instead of stdout.
-T, --timeoutPer-connection timeout in seconds.
--threadsWorker threads for multi-host scans (default 5).
-V, --verbosityLogging level (INFO, DEBUG, etc.).

A bulk scan of every host in a file, written to a report, looks like:

bash
ssh_scan -f hosts.txt -o ssh-audit-report.json --threads 10

Writing 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:

yaml
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:
  - none

Run 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:

bash
ssh -Q kex          # key-exchange algorithms this OpenSSH knows
sudo sshd -T | grep -iE 'kex|cipher|macs'   # what THIS daemon is configured to offer

For 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.

ToolMaintainedBest for
ssh_scanNo (archived 2022)Quick remote read of offered algorithms; legacy pipelines that already parse its JSON
ssh-auditYesModern remote audits: grades, CVE flags, missing-strong-algorithm warnings
ssh -Q + sshd -TYes (ships with OpenSSH)Local checks on a box you already control, zero dependencies

FAQ

See also

Sources

Authoritative references this article was fact-checked against.

Tagsssh_scanSSHOpenSSHSecurityAuditingMozillaLinuxHardening

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years building software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Currently Chief Technology Officer at a healthcare tech startup, which is where most of these field notes come from.

Keep reading

Related posts