How to harden the SSH server¶
This guide changes the OpenSSH server (sshd) so that only members of one group can log in, only with a public key, never directly as root, and with tighter limits on failed and unfinished logins.
All the changes go in one drop-in file under /etc/ssh/sshd_config.d/, so they are easy to review, survive package upgrades that replace sshd_config, and can be undone by deleting one file.
Before you begin¶
- You administer the server from an ordinary (non-root) account that can use
sudo, that account already logs in over SSH with a key, and you are connected with it now. In the commands below,<USER>is that account's name. - If you currently log in only as
root, first create such an account, give itsudorights and a key, and check that you can log in with it and runsudo. This guide turns off root logins, so without that account you would be locked out. - You have a way back in that doesn't depend on SSH, such as a virtual machine console or out-of-band management, in case something goes wrong.
Keep your current session open
These changes can lock you out of the server.
Keep the SSH session you make the changes in open until you have confirmed, from a second session, that you can still log in with your key.
Reloading sshd doesn't disconnect existing sessions, so the first one remains your way back if the new settings refuse you.
After the reload, only accounts in the ssh-users group can log in. Any other account that uses SSH today (other administrators, deployment or backup jobs, Git or SFTP users) stops working unless you add it in step 3.
Steps¶
-
Check that the main configuration file reads the drop-in directory:
The default
sshd_configon Debian, Ubuntu, AlmaLinux, Rocky Linux and Arch has this line before any active (uncommented) setting. If the command prints nothing, the file was probably kept from an older release or replaced locally. Add the line above before the first active setting in/etc/ssh/sshd_configbefore you continue; otherwise the drop-in file is ignored.On Gentoo the line (with the path in quotes) is near the end of the file, after the commented-out defaults, with a note that all
Matchblocks must come after it. That's fine as long as those defaults stay commented out: a setting you uncomment above theIncludeline is read first and wins over the drop-in file. -
Find the accounts that log in over SSH today. List the accounts that have an
authorized_keysfile:$ getent passwd | while IFS=: read -r name _ _ _ _ home _; do if sudo test -s "$home/.ssh/authorized_keys"; then echo "$name"; fi; done labuserThen look for successful logins in the server log:
With OpenRC,
sshdlogs to the system logger. With Gentoo'ssysklogdconfiguration, authentication messages are in/var/log/auth.log:$ sudo grep -E 'Accepted (publickey|password|keyboard-interactive) for' /var/log/auth.log Nov 7 10:15:02 server sshd-session[1342]: Accepted publickey for labuser from 192.0.2.10 port 50122 ssh2: ED25519 SHA256:…With the systemd profile (not verified), use the
journalctlcommand from the other tabs.Logs only go back as far as they are kept, so also ask the people and check the automation (deployment, backups, monitoring, Git or SFTP users) that connect to this server. Note every account that must keep SSH access, and whether it logs in with a password: those accounts need a key before you continue.
-
Create a group for the accounts that may log in over SSH, and add your own account to it:
$ sudo groupadd ssh-users $ sudo usermod -aG ssh-users <USER> $ id <USER> uid=1000(labuser) gid=1000(labuser) groups=1000(labuser),1001(ssh-users)Check that
ssh-usersappears in the output. Add every other account you noted in step 2 in the same way, withsudo usermod -aG ssh-users <ACCOUNT>, where<ACCOUNT>is the account's name.sshdlooks up group membership for every new login, so you don't need to log out and back in for this to take effect. -
Create the file
/etc/ssh/sshd_config.d/00-hardening.confwith this content:# Local SSH hardening. sshd uses the first value it reads for each # keyword, so this file is named to sort before other drop-ins. PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no AllowGroups ssh-users MaxAuthTries 3 LoginGraceTime 30PermitRootLogin norefuses logins as root by any method. Administrators log in as themselves and usesudo.PasswordAuthentication noandKbdInteractiveAuthentication notogether turn off passwords. Both are needed: with PAM, a password can also be accepted through keyboard-interactive authentication. Gentoo's default configuration, for example, disables the first but leaves the second enabled.AllowGroups ssh-usersrefuses every account that isn't in the group, including system and service accounts.MaxAuthTries 3closes a connection after three failed attempts (the default is 6).LoginGraceTime 30gives a client 30 seconds to authenticate (the default is 120).
For most keywords
sshdkeeps the first value it finds, and theIncludeline reads drop-ins in name order. The00-prefix makes this file win over the distribution's own drop-ins (such as50-cloud-init.confor50-redhat.conf) and over the settings insshd_configthat come after theIncludeline. -
Check the configuration for errors:
No output means the configuration is valid. Don't reload the service until this command succeeds.
-
Reload the SSH service so it reads the new configuration:
On Debian and Ubuntu the service is called
ssh. (On Ubuntu it's started throughssh.socket; reloadingsshis still correct for these settings.)Your current session stays connected.
-
Keeping the first session open, open a second terminal and log in again from your workstation, where
<SERVER>is the server's host name or address:If your agent holds several keys,
sshmay use up the three attempts allowed byMaxAuthTriesbefore it offers the right one, and fail withToo many authentication failures. In that case name the key, withssh -o IdentitiesOnly=yes -i ~/.ssh/<KEY_FILE> <USER>@<SERVER>, where<KEY_FILE>is your private key file.If this works, close the first session only when you're done. If it doesn't, use the first session to fix the problem, as described in Troubleshooting.
Verify¶
-
Show the settings
sshdnow uses:$ sudo sshd -T | grep -Ei '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|allowgroups|maxauthtries|logingracetime) ' logingracetime 30 maxauthtries 3 permitrootlogin no passwordauthentication no kbdinteractiveauthentication no allowgroups ssh-userssshd -Tprints the configuration it would use, after reading every file. Newer OpenSSH releases print the keywords in mixed case (PermitRootLogin no), which is why the command ignores case. If a value is different, a setting read before your drop-in file wins; on Gentoo, check for uncommented settings above theIncludeline (see step 1). -
Confirm that the running server no longer offers passwords. On the server, try to log in to your own account without a key:
$ ssh -o PubkeyAuthentication=no -o BatchMode=yes -o StrictHostKeyChecking=accept-new localhost true Warning: Permanently added 'localhost' (ED25519) to the list of known hosts. labuser@localhost: Permission denied (publickey).The list in brackets is the authentication methods the server still accepts. It should contain
publickeyand notpasswordorkeyboard-interactive. On RHEL, AlmaLinux and Rocky Linux it also containsgssapi-keyex,gssapi-with-mic(Kerberos), which those distributions enable by default.
Troubleshooting¶
sshd -t reports an error.
It names the file and line. For example, after writing maybe instead of yes or no on line 4:
Fix that line and run sudo sshd -t again. The running server is unaffected until you reload it.
A new session is refused after the reload.
Look at the server log from your first session. On systemd distributions, run sudo journalctl -u ssh (Debian and Ubuntu) or sudo journalctl -u sshd (the others); on Gentoo with OpenRC and sysklogd, look in /var/log/auth.log.
A line such as User alice from 203.0.113.10 not allowed because none of user's groups are listed in AllowGroups means the account isn't in ssh-users.
If you can't find the cause, undo the change from the first session, then reload the service as in step 6:
Too many authentication failures.
The client offered more keys than MaxAuthTries allows before reaching the right one; every key an agent holds counts as an attempt.
Tell the client which key to use, with ssh -i ~/.ssh/<KEY_FILE> -o IdentitiesOnly=yes as in step 7, or with IdentityFile and IdentitiesOnly yes for that host in ~/.ssh/config.
A setting doesn't change in sshd -T.
Another file, or a line in sshd_config above the Include line, sets the same keyword earlier. Find it with sudo grep -ri '<KEYWORD>' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/, where <KEYWORD> is the setting's name, and rename your drop-in so that it sorts first, or remove the other setting.
A Match block in any file can also override settings for some connections; see the settings for a specific connection with sudo sshd -T -C user=<USER>,host=<SERVER>,addr=<ADDRESS>, where <ADDRESS> is the client's IP address.
Related¶
- Tutorial: Log in with SSH keys
- Reference: OpenSSH server configuration directives
- Explanation: How SSH authentication works