Auditing Stale SSH Keys with ssh-keygen -l and Protecting with fail2ban
SSH keys that sit unused for months can become a silent backdoor if a private key ever gets compromised. On a production server the safest way to keep the key list lean is to audit it regularly and block any brute‑force attempts that surface while you’re cleaning up.
1. List all authorized keys with fingerprints
The authorized_keys file lives in each user’s ~/.ssh/. To see which keys are actually present, run:
# Show fingerprints for all keys in /root/.ssh/authorized_keys
ssh-keygen -l -f /root/.ssh/authorized_keys
The -l flag prints a short fingerprint and the key type. If you want a more detailed view (including the comment field), add -E md5 or -E sha256:
ssh-keygen -l -E sha256 -f /root/.ssh/authorized_keys
Output looks like:
2048 SHA256:3Zx... [email protected] (RSA)
4096 SHA256:8Yz... alice@workstation (ECDSA)
Take note of the comment field; it usually tells you who owns the key. If you see an entry that no longer matches an active user or device, flag it for removal.
2. Identify stale keys by age
ssh-keygen itself can’t tell you when a key was added, but you can infer age by checking the modification time of the authorized_keys file and the timestamps of the individual key files if you store them separately. A common practice is to keep each key in its own file and use a symlink in authorized_keys:
# Example layout
/root/.ssh/keys/
├── [email protected]
├── [email protected]
Then run:
find /root/.ssh/keys/ -type f -mtime +90 -print
This lists keys older than 90 days. Adjust the threshold to your policy.
3. Remove or rotate stale keys
Once you’ve identified a key, remove it from authorized_keys and delete the file:
sed -i '/[email protected]/d' /root/.ssh/authorized_keys
rm /root/.ssh/keys/[email protected]
If you prefer to keep a backup, move it to a secure archive instead of deleting outright.
4. Block brute‑force attempts with fail2ban
Even with a clean key list, attackers may try to guess passwords or use stolen keys. fail2ban watches logs and bans IPs that trigger a pattern. For SSH, the default jail is usually already enabled. Verify it:
sudo fail2ban-client status sshd
If it’s not active, enable it:
sudo systemctl enable --now fail2ban
Customizing the SSH jail
The default sshd.conf in /etc/fail2ban/jail.d/ can be tweaked. For example, increase the ban time and limit the number of retries:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400 # 24 hours
After editing, reload:
sudo fail2ban-client reload
5. Verify the setup
Attempt a failed login to trigger a ban:
ssh baduser@localhost
Then check the status:
sudo fail2ban-client status sshd
You should see the offending IP in the Ban list. If you’re using a firewall that blocks the IP, the ban is effective.
6. Automate the audit
Add a cron job that runs the key audit script nightly and emails you a report:
0 2 * * * /usr/local/bin/ssh-key-audit.sh | mail -s "SSH Key Audit" [email protected]
ssh-key-audit.sh can wrap the ssh-keygen -l command and compare against a whitelist.
7. Caveats and troubleshooting
| Issue | Fix |
|---|---|
ssh-keygen -l fails on a key file |
Ensure the file is in OpenSSH format; convert with ssh-keygen -i -f key.pub |
| fail2ban not banning | Check /var/log/fail2ban.log for errors; confirm sshd log path matches logpath |
| Keys removed accidentally | Keep a backup directory; use git to version the authorized_keys file |
8. Resources
- Official OpenSSH documentation: https://www.openssh.com/manual.html
- fail2ban GitHub repo: https://github.com/fail2ban/fail2ban
- Debian fail2ban guide: https://www.debian.org/doc/manuals/debian-reference/ch04.en.html
By combining a regular key audit with fail2ban’s real‑time protection, you keep your production servers free from orphaned credentials and shielded against brute‑force attacks.
See also
- Automating Let's‑Encrypt renewal on a single‑node web server without downtime
- When My Ubuntu 24.04 VPS Runs Out of Disk: A Step‑by‑Step Cleanup Script That Keeps Your Apache Logs from Failing
- When a VPS Disk Fills Mid‑Night: Quick Recovery with LVM Snapshots and Automated Alerts
- Rootless Podman volumes: how to keep data safe across upgrades without root
- Fixing ProxyJump timeouts when a VPN drops your IP address.