Patches before anything else
No setting in sshd_config fixes a vulnerable daemon. Two recent examples:
- regreSSHion (CVE-2024-6387). A race condition in how sshd handles signals let anyone run code as root on glibc-based Linux, without logging in. OpenSSH 8.5p1 up to 9.7p1 were affected.
- Terrapin (CVE-2023-48795). An attacker in the network path could cut messages out of the start of an SSH session when ChaCha20-Poly1305 or Encrypt-then-MAC modes were used. OpenSSH 9.6 added "strict key exchange" to stop it, and both ends need it.
Distributions often backport fixes without changing the version number. So check the package changelog or the distribution's security tracker. ssh -V alone can mislead you.
The baseline
We put our settings in a drop-in file, so a package update doesn't overwrite them:
# /etc/ssh/sshd_config.d/10-baseline.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
AllowGroups ssh-users
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowAgentForwarding no
Test the configuration before reloading, and keep your current session open until a second login works:
sshd -t && systemctl reload sshd
On distributions that use drop-in files, check that the main sshd_config includes them near the top. For most options sshd keeps the first value it reads, so an earlier line can quietly win.
Keys
- Ed25519 keys for people. One key per person per device, with a passphrase.
- No shared accounts. Admin work goes through personal accounts and
sudo, so the logs show who did what. - A list of authorised keys per server, gone through whenever someone leaves. An old key in
authorized_keysis a way in that nobody remembers. - Keys for deployments and automation restricted in
authorized_keyswithfrom=,command=andrestrictwhere possible.
Algorithms
Current OpenSSH defaults are fine. We only remove old algorithms where some old client once forced them back in. ssh-audit shows what the server offers and flags the weak ones:
ssh-audit server.example.com
Who can reach port 22
Moving SSH to another port cuts down the noise in the logs, and that's about all it does. What helps is limiting who can connect at all:
- Allow port 22 only from known addresses at the firewall, or put it behind a VPN or a bastion host.
- Rate-limit or ban repeated failures (fail2ban, or limits in nftables).
- On hosting platforms where shell access is optional, leave it off for accounts that don't need it.
The same checks every time
We check the same things on every server and write the results into the runbook or the audit report:
| What | How |
|---|---|
| The configuration actually in force | sshd -T \| grep -Ei 'permitroot\|passwordauth\|allowgroups' |
| Package and patch level | the distribution's security tracker |
| Authorised keys | find /home /root -name authorized_keys -exec wc -l {} + |
| Algorithms offered | ssh-audit |
| Recent logins | last -a, journalctl -u sshd |
sshd -T prints the configuration after every include has been read. That's the one sshd actually uses.