Who can SSH into your server right now? You probably don't know.
Ask yourself a simple question about any server you run: how many SSH keys can open it, and whose are they?
Most people can't answer. Keys accumulate — the contractor from 2024, the CI system you migrated off, the laptop you wiped last year, that one key you added "temporarily" during an incident. Nothing ever removes them. And unlike passwords, SSH keys don't expire: a key added five years ago works exactly as well today.
Meanwhile the internet keeps knocking. Honeypot studies routinely log tens of millions of SSH brute-force attempts in a few months; if your box has port 22 open to the world, it's being probed right now. An access audit is an hour of work. Here's the whole thing.
Step 1: Enumerate every key on the box
The mistake is checking only your own ~/.ssh/authorized_keys. Keys can live under any user with a shell — including service accounts you forgot exist:
sudo find / -name authorized_keys -not -path "/proc/*" 2>/dev/null \
| while read f; do echo "== $f"; sudo cat "$f"; done
Fingerprint them so you can talk about them precisely:
ssh-keygen -l -f /home/deploy/.ssh/authorized_keys
Now the uncomfortable part: for each key, name the human who holds the private half. The comment field at the end of each line is the only clue you get — Linux stores no "date added" for a key. Any key you can't attribute to a person who still needs access is a finding. In audits of real fleets, the departed-employee key is the single most common one.
Step 2: Read what sshd actually enforces
Not what the config file says — what the running daemon resolved, includes and all:
sudo sshd -T | grep -E "permitrootlogin|passwordauthentication|maxauthtries|x11forwarding"
Three answers matter most:
passwordauthentication yesmeans every one of those brute-force bots gets to play the lottery. This is the highest-value single change on the list.permitrootlogin yeshands attackers the one username that exists on every Linux box, and erases your audit trail — everything root does is just "root."maxauthtries 6gives six guesses per connection. Three is plenty for a human with the right key.
Step 3: Check the escalation path
A key gets an attacker in the door; sudo decides what they become. One line to hunt for:
sudo grep -rE "NOPASSWD" /etc/sudoers /etc/sudoers.d/ | grep -v "^#"
(ALL) NOPASSWD: ALL on a service account means any compromise of that account — a web app RCE, a leaked deploy key — is instantly root, no password required. If automation genuinely needs passwordless sudo, scope it to exact commands: NOPASSWD: /usr/bin/systemctl restart nginx, not ALL.
Step 4: Look at who's actually been getting in
last -a | head -20 # recent successful logins, with source IPs
sudo lastb --ip | head # failed attempts — meet your attackers
w # who's on right now
lastb is grimly educational the first time: pages of root, admin, oracle, test from IPs you've never heard of. That's the background radiation your config has to survive.
Hardening without locking yourself out
Every SSH hardening story has a version where the author cuts off the branch they're sitting on. The discipline that prevents it:
- Keep your current session open the whole time. A live session survives an sshd restart; it's your escape hatch if the new config is broken.
- Confirm key login works from a second, new terminal before disabling password auth.
sudo sshd -tafter every edit — it catches syntax errors before they take down the daemon.systemctl reload ssh, test a fresh login, and only then close the old session.
One directive at a time. The lockout stories all start with someone changing five things and restarting once.
Making it a habit instead of a heroic effort
The audit above is genuinely easy — it's eight commands. The reason nobody does it quarterly is that it's eight commands times every host you run, plus the cross-referencing, plus remembering what sshd -T output is supposed to look like.
This is the kind of job Tryssh collapses into a sentence. Ask the copilot "audit ssh access on this box — who can get in and how?" and it runs the enumeration for you: every authorized_keys on the host, the effective sshd config, the sudoers grep, the login history. All of it is read-only, so it runs without ceremony — and the copilot hands you findings, not raw output:
$ sudo sshd -T | grep -E "permitrootlogin|passwordauth" permitrootlogin yes passwordauthentication yes $ sudo find /home /root -name authorized_keys /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys $ ssh-keygen -l -f /home/deploy/.ssh/authorized_keys 256 SHA256:kP3m…Qw9E anandesh@mbp (ED25519) 256 SHA256:8xVn…LmC2 ci-github-2024 (ED25519) 256 SHA256:Jr7t…9dKf temp-key (ED25519) $ sudo grep -rE "NOPASSWD" /etc/sudoers /etc/sudoers.d/ (none) $ sudo lastb --ip | wc -l 2113
The fixes are where it stops. PasswordAuthentication no, deleting that stale key line, tightening sudoers — each renders as an exact change and waits for your approval, with your session held open. The gate that keeps the agent from breaking things is the same discipline that keeps you from locking yourself out. That's not a coincidence; it's the product.
Tryssh is a native macOS SSH workspace with an agentic copilot. Download it free — the terminal is never metered.