OpenSSH hardening checklist
Settings I apply to every new server. Some of these are already the modern defaults
but I set them explicitly so the config is self-documenting. All of this goes in
/etc/ssh/sshd_config (or a file in /etc/ssh/sshd_config.d/).
Always test in a second terminal before restarting sshd. Always.
Key-based authentication setup
Generate a key pair locally if you don't have one. Ed25519 is preferred — smaller keys, faster, more modern than RSA:
ssh-keygen -t ed25519 -C "$(hostname)-$(date +%Y%m%d)"
Copy the public key to the server:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
Or manually append it to ~/.ssh/authorized_keys on the server.
Verify you can log in with the key before doing anything else.
sshd_config settings
Disable password and root login
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication no covers keyboard-interactive auth, which
some PAM configs use as a backdoor around PasswordAuthentication no.
Set both to be safe.
Limit authentication attempts
MaxAuthTries 3
MaxSessions 5
MaxAuthTries 3 reduces the window for automated credential stuffing.
The default is 6, which is more than enough for a typo but also more than enough
for a fast brute-force attempt per connection.
Restrict what users can do
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
Disable features you're not using. If you need agent forwarding for a specific workflow, enable it consciously rather than leaving it on by default.
Connection keepalive
ClientAliveInterval 120
ClientAliveCountMax 3
Server sends a keepalive packet every 120 seconds. After 3 missed responses (6 minutes of silence) it drops the connection. Prevents ghost sessions from accumulating.
Miscellaneous
LoginGraceTime 30
PrintLastLog yes
Banner none
LoginGraceTime 30 — unauthenticated connections are dropped after
30 seconds. Default is 120, which is a long time to leave a connection hanging.
Banner none — no need to announce what software you're running.
Restricting access by user
If there's only one account that needs SSH access, lock it down explicitly:
AllowUsers youruser
This causes an implicit DenyUsers * for everyone else. More explicit
than relying on key auth alone.
Verify config before restarting
# Check config for syntax errors
sshd -t
# If clean, reload (less disruptive than restart — doesn't kill existing sessions)
systemctl reload ssh
Checking active connections
# Who is currently connected
who
w
# Active SSH sessions via ss
ss -tnp | grep :22
# Recent logins
last | head -20
fail2ban for brute force
Even with password auth disabled, automated SSH scanners will keep hammering port 22. fail2ban watches the auth log and temporarily bans IPs that fail too many times. On Debian with nftables:
apt install fail2ban
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
banaction = nftables
banaction_allports = nftables[type=allports]
bantime.increment = true
bantime.maxtime = 1w
The sshd jail is enabled by default on Debian. See the
fail2ban reference page for status and management
commands.