SSH is the front door to every Linux server you operate. Get it right and the box can sit on the public internet for years. Get it wrong and a botnet has root in minutes. The defaults shipped by every distribution are conservative for compatibility β production hardening is a 30-line config change plus two additional tools. This guide walks through the sshd_config settings that matter, the public-key workflow, and the brute-force defenses that turn a 24/7 password-spray attack into a non-event.
The 12 settings every server should have
Edit /etc/ssh/sshd_config.d/99-hardening.conf (drop-in directory exists on Ubuntu 22.04+ and RHEL 9+) so distribution upgrades do not overwrite your changes:
Port 22 # change ONLY if you also have a jump host
Protocol 2
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM yes # required for PAM tally / faillock
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
MaxSessions 10
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers deploy ops admin
PermitEmptyPasswords no
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no # set to 'yes' only if you actually need it
PrintLastLog yes
LogLevel VERBOSE # records key fingerprint per login
Apply: sudo sshd -t && sudo systemctl reload ssh. sshd -t is mandatory β a typo is the difference between "reload succeeded" and "you are locked out forever."
Modern KEX, ciphers, and MACs
Restrict to algorithms recommended by Mozilla's "intermediate" SSH guidance:
KexAlgorithms curve25519-sha256@libssh.org,curve25519-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
Test the negotiated cipher: ssh -vv user@host 2>&1 | grep 'cipher:'.
Public-key workflow done right
ssh-keygen -t ed25519 -C "user@laptop" # client side
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host # ships to authorized_keys
chmod 700 ~/.ssh # required perms
chmod 600 ~/.ssh/authorized_keys
Use Ed25519. RSA is fine if >=3072 bits, but Ed25519 is shorter, faster, and equally strong. DSA and ECDSA P-256 are deprecated.
Add command= and from= restrictions in authorized_keys for restricted automation users:
from="10.0.0.0/8",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="/usr/local/bin/backup-only.sh" ssh-ed25519 AAAAβ¦
Brute-force defense in depth
- fail2ban watches sshd logs and bans source IPs at the firewall after configurable failure counts:
# /etc/fail2ban/jail.d/sshd.local [sshd] enabled = true maxretry = 5 findtime = 10m bantime = 24h banaction = iptables-multiport ignoreip = 127.0.0.1/8 10.0.0.0/8 - PAM faillock locks the user account after N failures regardless of source β defense against an attacker rotating IPs:
# /etc/security/faillock.conf deny = 5 unlock_time = 900 even_deny_root - Network ACL β restrict who can even reach port 22. If your team has a VPN, deny everything else:
sudo ufw default deny incoming sudo ufw allow from 198.51.100.0/24 to any port 22 sudo ufw enable
Multi-factor authentication
For high-value servers, layer TOTP onto public-key auth. Install libpam-google-authenticator on Debian/Ubuntu (or its RHEL equivalent), enroll users with google-authenticator, then:
# /etc/pam.d/sshd β at top
auth required pam_google_authenticator.so nullok
# /etc/ssh/sshd_config.d/99-mfa.conf
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
Combination: a stolen private key alone is no longer enough.
Detect compromise
sudo grep 'Accepted publickey' /var/log/auth.log | awk '{print $9, $11}' | sort -u
sudo lastb -i | head # failed root attempts
sudo journalctl _COMM=sshd --grep='Failed password' --since '1 hour ago'
sudo find / -name 'authorized_keys' -newer /etc/ssh/sshd_config 2>/dev/null
The last command surfaces any authorized_keys file modified after sshd's config β a strong signal of an attacker dropping their own key.
Audit your hardening with ssh-audit
pip install ssh-audit
ssh-audit yourserver.com
Output graded A through F per algorithm class. Keep iterating until you reach A across the board on internet-facing hosts.
Common pitfalls
- Disabling password auth without first deploying keys β the next reboot locks you out.
- Setting
PermitRootLogin without-passwordand forgetting it;nois correct for almost everyone. - Allowing
AllowTcpForwarding yesby default; an attacker uses it to pivot from a low-privilege box to the database. - Putting fail2ban on a server where the only sshd port is behind a load balancer β every ban is the LB's IP.
SSH hardening is the single highest-ROI security investment a Linux administrator can make. The 30 lines of config above, plus fail2ban and a key-only policy, eliminate the vast majority of opportunistic compromise. Apply, validate with ssh-audit, and keep logs flowing somewhere off-box.