Server--:--:--You--:--:--

SSH Server Hardening

By Prabath Thalangama· September 16, 2026· 3 min read
#linux#ssh#security

Introduction

SSH is the front door to most servers. Hardening it is high-value and mostly a matter of a dozen sshd_config lines. The one rule: keep a second session open while you test, so a mistake doesn't lock you out.

The priority order

1. Key-only authentication

PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no        # was ChallengeResponseAuthentication
AuthenticationMethods publickey

This alone eliminates password brute-forcing. Make sure your key works before disabling passwords.

2. No direct root login

PermitRootLogin no
# or, if automation needs it, key-only + forced command:
# PermitRootLogin prohibit-password

Log in as a normal user, sudo up.

3. Limit who can log in

AllowUsers alice bob deploy
# or
AllowGroups sshusers
DenyUsers *

Explicit allow-lists beat "everyone except".

4. Modern crypto

Recent OpenSSH defaults are already good; be explicit if you have a compliance need:

KexAlgorithms [email protected],curve25519-sha256,[email protected]
Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected]
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512

Use an ed25519 host key and user keys; drop DSA and small RSA.

5. Reduce exposure

LoginGraceTime 20
MaxAuthTries 3
MaxSessions 4
MaxStartups 10:30:60        # start dropping new unauthed conns under flood
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no        # unless you need it
PermitTunnel no

6. Rate-limit at the firewall / fail2ban

# nftables
nft add rule inet filter input tcp dport 22 ct state new \
  meter ssh { ip saddr limit rate 6/minute } accept

# or fail2ban's sshd jail (see the fail2ban post)

Changing the port to 2222 reduces log noise but isn't real security — do it if you like, keep everything above.

7. Match blocks for exceptions

Match User backup
    ForceCommand /usr/local/bin/rrsync -ro /srv/backups
    PermitTTY no
    AllowTcpForwarding no

Match Group sftponly
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no

8. SSH certificates (at scale)

Instead of distributing authorized_keys everywhere, run a small CA and sign user keys with short lifetimes:

TrustedUserCAKeys /etc/ssh/ca_user_key.pub
ssh-keygen -s ca_user_key -I [email protected] -n alice,deploy -V +8h alice_key.pub

Now alice logs in anywhere the CA is trusted, and access auto-expires in 8h. Revocation via RevokedKeys.

Applying safely

sudo sshd -t                       # syntax check
sudo sshd -T | grep -Ei 'permitroot|password|pubkey'   # effective config

# Keep your current session. Restart in a way you can undo:
sudo systemctl restart ssh         # (or reload)
# In a NEW terminal, confirm you can still log in:
ssh -v you@server

If the new session fails, fix it from the still-open one. Some people add a sleep 300 && systemctl restart ssh.bak safety timer, or use systemd-run with a timeout.

Verification and troubleshooting

ssh -vvv you@server                # client-side: which auth methods, key offered
sudo journalctl -u ssh -f          # server-side: why an auth failed
sudo sshd -T                       # the actual running config values
  • Locked out after PasswordAuthentication no — key wasn't actually working. Recover via console/cloud serial console/rescue; check ~/.ssh/authorized_keys perms (dir 700, file 600, owned by the user) and sshd -T | grep pubkey.
  • Permission denied (publickey) — key not in authorized_keys, wrong key offered (IdentityFile / ssh-add -l), home dir group-writable (sshd refuses), or SELinux context on .ssh (restorecon -Rv ~/.ssh).
  • Match block not applyingMatch must come after the global settings; everything after a Match until the next Match/EOF is conditional.
  • Forwarding disabled broke a workflow — re-enable per-user with a Match block rather than globally.
  • fail2ban banned youfail2ban-client set sshd unbanip <ip>; whitelist your admin ranges in ignoreip.
  • Slow loginUseDNS no (reverse-DNS lookup on connect), GSSAPIAuthentication no if not using Kerberos.
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.