Keep a session open while you test
Reloading sshd does not end existing connections. In our test, a session opened before the reload kept running and finished normally after it. So leave one root-capable session open, and do all testing from a second terminal. If a setting locks you out, you fix it from the first one.
Before you set PasswordAuthentication no, make sure a key login works. See how to disable SSH password login.
Use a drop-in file, and know that the first value wins
For most settings, sshd keeps the first value it reads. Debian and Ubuntu have Include /etc/ssh/sshd_config.d/*.conf at the top of sshd_config, so a drop-in beats a line further down the main file. On our test system the main file had PasswordAuthentication yes on line 125, and the drop-in with no won.
Files in that folder are read in name order, and some cloud images add their own, so a low number such as 10- is a safe choice. If your system has no Include line, edit sshd_config directly and put the settings before any Match block.
The settings
# /etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowUsers deploy
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
| Setting | Upstream default | What the change does |
|---|---|---|
PermitRootLogin no | prohibit-password | Root cannot log in at all, even with a key. People log in as themselves and use sudo, which leaves a record of who did what. |
PasswordAuthentication no | yes | Removes password guessing as an attack. |
KbdInteractiveAuthentication no | yes | Closes the other route to a password prompt. Leave it on if you use one-time codes through PAM. |
AllowUsers deploy | Not set: any account can try | Only the listed accounts can log in. A typo here locks everyone out, which is why the first session stays open. |
MaxAuthTries 3 | 6 | Fewer attempts per connection. Each key offered by a client counts, so a client with many keys can run out. See Too many authentication failures. |
LoginGraceTime 30 | 120 seconds | Unauthenticated connections are dropped sooner. |
X11Forwarding no | no, but Debian's file sets yes | Turns off a feature most servers never use. |
AllowAgentForwarding no | yes | See the risks of agent forwarding. |
AllowTcpForwarding no | yes | Stops SSH tunnels through this server, and its use as a jump host. Do not set this if you use either. |
The manual is clear about one limit: disabling TCP forwarding does not improve security unless users are also denied a shell, because anyone with a shell can run their own forwarder. Treat it as cleanup for accounts that have a shell, and as a real restriction only for accounts that do not.
Leave MaxStartups and PerSourcePenalties at their defaults unless you have a reason. They already slow down floods of connections on current versions.
Check before you reload
sshd -t checks the syntax and the host keys. It prints nothing when all is well. A typo gives an error and a non-zero exit code:
$ sudo sshd -t
/etc/ssh/sshd_config.d/99-typo.conf: line 1: Bad configuration option: PermitRootLogn
/etc/ssh/sshd_config.d/99-typo.conf: terminating, 1 bad configuration options
sshd -T prints the values sshd will really use, after all files are combined. Names appear in lowercase:
sudo sshd -T | grep -iE '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|allowusers|maxauthtries|logingracetime|x11forwarding|allowagentforwarding|allowtcpforwarding) '
If you use Match blocks, add -C to see the result for one particular connection:
sudo sshd -T -C user=deploy,host=example.com,addr=203.0.113.10 | grep -i allowtcpforwarding
sshd -T reads the files on disk, not what the running daemon loaded. It shows what you will get after the reload.
Reload and test from a second terminal
# Debian, Ubuntu
sudo systemctl reload ssh
# Red Hat family
sudo systemctl reload sshd
Then try each of these. The first must succeed and the others must fail:
ssh deploy@server 'echo ok'
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password deploy@server
ssh root@server
ssh -N -L 8022:127.0.0.1:22 deploy@server
The last command does not fail at login. It connects, and the forward is refused when you use the local port. The server log then shows a line like this:
refused local port forward: originator 127.0.0.1 port 63434, target 127.0.0.1 port 22
A refused root login appears in the log as User root from 203.0.113.10 not allowed because not listed in AllowUsers.
Modern algorithms: only if you need to restrict them
Current OpenSSH defaults are good. On OpenSSH 10.0 the default key exchange list starts with the hybrid post-quantum mlkem768x25519-sha256. Setting your own lists means you must maintain them, so do it when an audit requires it or when your system is old. This set keeps only modern choices:
KexAlgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
Older OpenSSH versions do not know some of these names. Run ssh -Q kex, ssh -Q cipher and ssh -Q mac on the server and remove what is not listed. Check what a client really negotiates:
ssh -v deploy@server true 2>&1 | grep -E 'kex: algorithm|server->client cipher'
Old clients and devices will fail with this message, which has its own guide:
Unable to negotiate with 203.0.113.10 port 22: no matching key exchange method found.
This list also removes the NIST curve and Diffie-Hellman exchanges, so test every device and script that connects to the server before you keep it.
What this does not cover
These settings shrink what can be tried against sshd. They do not patch OpenSSH, so keep it updated. They also do not limit what a logged-in user can do. For repeated attacks in your logs, see fail2ban for SSH.
To see who is trying, see how to check SSH login attempts.
Questions
Does reloading sshd close my current SSH session?
No. Existing sessions keep running after a reload. Only new logins use the new settings, which is why you test from a second terminal.
Where should I put my own sshd settings?
In a file in /etc/ssh/sshd_config.d/ with a low number, such as 10-hardening.conf. sshd keeps the first value it reads, so a drop-in read early wins.
How do I check sshd_config for errors?
Run sudo sshd -t. It prints nothing when the configuration is valid, and an error with a non-zero exit code when it is not.