SSH hardening checklist for production servers
A step-by-step skill for hardening SSH on production servers: key-only auth, disabling password and root login, limiting users, fail2ban, and safe config rollout. Use when an agent or engineer is asked to secure SSH access, audit sshd_config, or respond to brute-force alerts. Triggers: 'harden SSH', 'sshd_config audit', 'disable password auth', 'SSH brute force'. Not for: VPN setup, TLS certs, general server provisioning.
SSH hardening checklist for production servers
TL;DR
Switch to key-only auth, disable password login and root login, restrict which users can connect, and add fail2ban for brute-force noise. Test a new session before closing your current one, because a bad sshd_config plus a closed session equals a lockout and a very bad afternoon.
SSH hardening checklist for production serversUse this when
- You are setting up or auditing SSH on a production server
- Logs show brute-force attempts against sshd
- Compliance asks for an SSH hardening checklist with evidence
- You inherited a server and dont trust its sshd_config
Not for this skill when
- You need VPN or WireGuard setup (different skill)
- You are debugging SSH connectivity in general, not hardening
- You need TLS certificates for web services
- You are provisioning the whole server from scratch
Steps
1. Audit the current effective config
sshd_config files include drop-ins, so read the effective config, not just the file. Look for password auth, root login, and empty passwords.
sudo sshd -T | grep -Ei 'passwordauthentication|permitrootlogin|permitemptypasswords|pubkeyauthentication'Expected: lines like passwordauthentication yes or permitrootlogin prohibit-password. This is your before snapshot; anything saying yes where you want no goes on the fix list.
2. Enforce key-only auth and lock down root
Write a drop-in config so your changes survive package updates to the main file. Key-only auth kills password brute-forcing outright; disabling root login shrinks the target.
printf 'PasswordAuthentication no\nPermitRootLogin no\nPermitEmptyPasswords no\nPubkeyAuthentication yes\n' | sudo tee /etc/ssh/sshd_config.d/hardening.confExpected: the drop-in file exists with those four lines. If you genuinely need root over SSH (you probably dont), use prohibit-password instead of no, and revisit that decision quarterly.
3. Restrict who can log in
Default-deny beats default-allow. List the humans and service accounts that need SSH; everyone else gets refused at the door.
printf 'AllowUsers deploy [your-username]\n' | sudo tee -a /etc/ssh/sshd_config.d/hardening.confExpected: the AllowUsers line appended. Double-check the usernames exist (id [username]) before you restart anything; a typo here locks out legitimate users.
4. Harden keys and add brute-force protection
Generate ed25519 keys (short, fast, modern) and install fail2ban so the background brute-force noise gets banned instead of just logged.
ssh-keygen -t ed25519 -f [HOME]/... -N "" && sudo apt-get install -y fail2ban && sudo systemctl enable --now fail2banExpected: a new keypair at that path, and fail2ban running (sudo fail2ban-client status sshd shows a jail). Copy the public key to the server's authorized_keys before you disable passwords.
5. Validate, restart, and test before closing your session
Check the config syntax, restart sshd, then open a NEW session while the old one is still alive. Only close the old session after the new one works.
sudo sshd -t && sudo systemctl restart sshd && ssh -i [HOME]/... [username]@YOUR_HOST 'echo SSH_OK'Expected: sshd -t silent (valid), sshd restarts, and the new connection prints SSH_OK. If the new session fails, your old session is still open; fix the config and retry.
Variant: sshd_config best practices
The full settings list people search for: Protocol 2 (default now), X11Forwarding no, MaxAuthTries 3, LoginGraceTime 60, ClientAliveInterval 300, ClientAliveCountMax 2, AllowTcpForwarding scoped or no. Apply via the same drop-in pattern and validate with sshd -T.
Variant: disable password auth ssh
The minimal version of this skill: set PasswordAuthentication no plus ChallengeResponseAuthentication no (keyboard-interactive can otherwise still prompt), confirm your key works first, restart, done. This one change stops effectively all password brute-forcing.
Variant: ssh brute force protection
If you cant change auth methods right now, fail2ban plus MaxAuthTries 3 plus moving off port 22 cuts the noise dramatically. Moving ports is obscurity, not security, but it deletes 99 percent of automated attempts from your logs.
Why this happens
SSH is the front door every bot on the internet knocks on, and password auth lets them knock millions of times. Key-only auth changes the game from "guessable secret" to "unforgeable credential," and everything else on the checklist (no root, limited users, fail2ban) is about shrinking what a compromised credential or a bug can reach.
Edge cases and pitfalls
- Locking yourself out: always test a new session before closing the old one; keep a console (cloud provider web console) path as a backstop.
- Automation that uses passwords: CI deploy keys and scripts break when passwords go away; migrate them to keys first.
ChallengeResponseAuthenticationleft on: keyboard-interactive can still prompt for passwords even with PasswordAuthentication off; disable both.- AllowUsers typos: one wrong username and a legit user cant get in; verify with
idfirst. - fail2ban banning you: whitelist your office and CI IPs in the fail2ban config before enabling aggressive bans.
- Old clients: very old SSH clients may not speak ed25519; keep one RSA key around if you still manage legacy boxes.
Provenance
Resolved from the public thread: https://vectle.com/posts/pstEQM-clmB4tBcDDmEn8Ijg
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.