SSH hardening without locking yourself out: keys, root, fail2ban
The goal is simple: only people holding a private key can log in (SSH key authentication), root cannot log in with a password, and only named accounts are allowed. The risk is locking yourself out, so do it in a fixed order and keep one working session open until a fresh login succeeds.
The safe order
Before you start, make sure you have a way in that does not depend on SSH: the hosting provider's web console, IPMI, or physical access. If everything goes wrong, see Locked out of SSH.
Create and install a key
On your own computer, not the server, create an Ed25519 key. It is the default type in current OpenSSH and is short and fast. Protect it with a passphrase.
# on your workstation
ssh-keygen -t ed25519 -a 100 -C "alice@laptop"
# copy the public key to your account on the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub alice@server.example.com
# test: this must log in without asking for the account password
ssh -o PreferredAuthentications=publickey alice@server.example.com
sudo -v # on the server: confirm you can still use sudo
-a sets the number of key-derivation rounds that protect the passphrase; more rounds make a stolen key file slower to crack. Check old keys too: OpenSSH 10.0 removed DSA support entirely, so ssh-dss keys in authorized_keys no longer work and should be deleted.
Where to put settings
Don't edit the main /etc/ssh/sshd_config; put your settings in a drop-in file in /etc/ssh/sshd_config.d/. Package updates leave it alone, and it is easy to review.
One rule matters more than any other: for most keywords, sshd uses the first value it reads and ignores later ones (sshd_config(5): "the first obtained value will be used"). That decides which file wins:
- Debian and Ubuntu include
/etc/ssh/sshd_config.d/*.confat the very start ofsshd_config, so drop-ins override the main file. Files are read in name order, so10-hardening.confbeats a50-…file. - RHEL 9 and later ship
50-redhat.confin the same directory. Red Hat's documentation says to use a two-digit prefix below 50 so your file is read first, for example40-hardening.conf. - Cloud images, provisioning tools and hosting panels sometimes add their own file or edit
sshd_config. A strayPasswordAuthentication yesthat is read before yours wins.
So always check the effective result, not the file you wrote. sshd -T prints the configuration sshd will actually use:
sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|allowusers|allowgroups)'
The settings
Create /etc/ssh/sshd_config.d/10-hardening.conf (use 40-hardening.conf on RHEL):
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
AllowGroups sshusers
MaxAuthTries 3
PasswordAuthentication no- Turns off password logins. The upstream default is
yes. KbdInteractiveAuthentication no- Keyboard-interactive login is the other way a password prompt reaches PAM. Upstream defaults it to
yes(Debian's packaged config setsno). Leave it on only if you use it for MFA, below.ChallengeResponseAuthenticationis a deprecated alias. PermitRootLogin- The default,
prohibit-password, allows root only with a key. Usenoonce every admin has a named account with sudo; it also records who logged in.without-passwordis a deprecated alias ofprohibit-password. AllowGroups/AllowUsers- Only listed groups or users may log in at all. Create the group and add yourself first:
sudo groupadd sshusers,sudo usermod -aG sshusers alice. sshd checksDenyUsers,AllowUsers,DenyGroups, thenAllowGroups, and a user must pass all that are set.AllowUsersalso acceptsuser@addresspatterns, such asalice@203.0.113.*. MaxAuthTries 3- Fewer attempts per connection (default 6). If you have many keys loaded in your agent, pick the right one with
-iorIdentitiesOnly yes, or you may hit the limit.
Test and reload
# 1. syntax and key check: no output means OK
sudo sshd -t
# 2. confirm the effective values (see the grep above)
sudo sshd -T | grep -Ei '^(passwordauthentication|permitrootlogin|allowgroups)'
# 3. reload (existing sessions stay connected)
sudo systemctl reload ssh # Debian, Ubuntu
sudo systemctl reload sshd # RHEL family, Fedora, SUSE
Now, without closing your current session, open a new terminal and log in. Also confirm passwords are refused:
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password alice@server.example.com
# expected: Permission denied (publickey).
If the new login fails, fix the drop-in from the old session, run sshd -t and reload again.
Ubuntu and changing the port. Since Ubuntu 22.10, sshd is started by systemd socket activation (ssh.socket). On Ubuntu 24.04 and later, the Port and ListenAddress values are read from the sshd configuration by a systemd generator, so after changing them run sudo systemctl daemon-reload and sudo systemctl restart ssh.socket, and open the new port in the firewall first. Other settings only need the reload above. Moving SSH off port 22 cuts log noise but is not a security control on its own.
Limit where logins come from
If admins connect from fixed addresses or a VPN, allow port 22 only from those in the firewall. This stops almost all scanning and leaves fail2ban with little to do. See Firewall basics.
Brute-force protection
With passwords off, password guessing cannot succeed, but the noise fills logs and uses CPU. Two layers help:
- Built in: OpenSSH 9.8 and later penalize client addresses that repeatedly fail to authenticate or crash the session (
PerSourcePenalties, on by default). UsePerSourcePenaltyExemptListfor your monitoring hosts. - fail2ban reads the logs and blocks offending addresses in the firewall. It is in Debian and Ubuntu, and in EPEL for RHEL-family systems. Debian's package enables the
sshdjail by default. Never editjail.conf; put changes in/etc/fail2ban/jail.local:
[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 203.0.113.10
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip 203.0.113.10 # if you banned yourself
Put your own fixed address in ignoreip. The systemd backend reads the journal, which matters on systems without /var/log/auth.log. If you face an active attack, see Brute-force attacks.
MFA options
- FIDO2 security keys (MFA).
ssh-keygen -t ed25519-sk -O verify-requiredcreates a key that only works with the hardware token plugged in, and asks for its PIN on each use. This is phishing-resistant and needs no server changes beyond a normalauthorized_keysentry, but the token, client and server must support it. - Key plus a one-time code (TOTP). Ubuntu documents this with
libpam-google-authenticator: each user runsgoogle-authenticator, PAM getsauth required pam_google_authenticator.so, and sshd getsKbdInteractiveAuthentication yesandAuthenticationMethods publickey,keyboard-interactive. Every user must enroll before you switch it on, or they cannot do it later over SSH. - VPN or bastion host with MFA in front of SSH, so port 22 is never public.
Keep OpenSSH updated
sshd runs as root and faces the network, so its updates are urgent. In July 2024, OpenSSH 9.8 fixed "regreSSHion" (CVE-2024-6387), a race condition that could allow remote code execution as root in Portable OpenSSH 8.5p1 to 9.7p1. As of November 2026 the newest upstream release is OpenSSH 10.6 (October 2026), but distributions backport fixes into older version numbers, so judge by your vendor's advisory, not the version string. Install updates with the normal package tools, then restart sshd so the listener runs the new code (package scripts usually do this). See Check installed versions and Security advisories.
Sources
- sshd_config(5) manual page, OpenBSD
- sshd(8) manual page, OpenBSD
- ssh-keygen(1) manual page, OpenBSD
- sshd_config(5) with Debian defaults, Debian
- OpenSSH server, Ubuntu Server documentation
- SSHd now uses socket-based activation (Ubuntu 22.10 and later), Ubuntu Discourse
- Two-factor authentication with TOTP/HOTP, Ubuntu Server documentation
- Using secure communications between two systems with OpenSSH, RHEL 9 documentation
- OpenSSH 9.8 release notes, OpenSSH
- OpenSSH security, OpenSSH
- OpenSSH release notes, OpenSSH
- jail.conf, fail2ban project
- fail2ban-client(1) manual page, Debian