Harden SSH without locking yourself out
Most SSH hardening is three or four lines of config. The hard part is applying them over the same SSH connection they control. Do it in an order where every mistake is still fixable.
Published . Assumes OpenSSH on Ubuntu 22.04 or 24.04, or Debian 12 or later, managed by systemd.
- Why people lock themselves out
- Before you change anything
- Put your settings in a drop-in file
- Check the config before applying it
- Reload or restart
- Test with a fresh connection
- If you get locked out
Why people lock themselves out
sshd reads its config when it starts or reloads. Your current session keeps running on the old rules, so a bad change looks fine until you log out and try to come back. By then the only way in is your provider's console.
The fix is a habit, not a tool: keep a working session open, check the config before applying it, and test with a brand new connection before you close anything.
Before you change anything
- Log in with a key that works. If you still log in with a password, set up a key first: SSH keys with ed25519.
- Use a normal user with sudo. If you only ever log in as root, create a sudo user and test it before you turn root login off: use a sudo user instead of root.
- Find your provider's console. Most VPS and cloud panels have a web or serial console that doesn't go through SSH. Open it once now, so you know it works and you know the user's password.
- Open a second SSH session. Leave it logged in, with
sudo -ialready done. It is your way to undo a mistake.
Put your settings in a drop-in file
On Ubuntu and Debian, the first line of /etc/ssh/sshd_config is Include /etc/ssh/sshd_config.d/*.conf. Files there are read in name order, and for each setting sshd uses the first value it finds. So a file with a low number wins over both the main file and later drop-ins, including the 50-cloud-init.conf that many cloud images ship.
sudo nano /etc/ssh/sshd_config.d/00-hardening.conf
# keys only
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
# no direct root login
PermitRootLogin no
# limits
MaxAuthTries 3
LoginGraceTime 30
# optional: only these users may log in at all
# AllowUsers you deploy
What each one does, and the default if you leave it out:
PasswordAuthenticationandKbdInteractiveAuthentication: password-style logins. Upstream default isyes. Both need to benofor keys only. See disable SSH password authentication.PermitRootLogin: the default isprohibit-password, which already blocks root passwords but allows root with a key.noblocks root completely.MaxAuthTries: failed attempts per connection, default 6. Each key your agent offers counts. If you carry many keys, you may needIdentitiesOnly yeson your Mac for this host.LoginGraceTime: seconds to finish logging in, default 120.AllowUsers: by default every user may log in. Once set, only the listed names can. Forget yourself here and you are out, so add it last and only if you need it.
Check the config before applying it
sshd -t checks the config files and host keys and prints nothing if they are fine. sshd -T also prints the settings sshd will actually use, after every include.
sudo sshd -t
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|maxauthtries|logingracetime|allowusers)'
The output keywords are lowercase. If a value isn't what you wrote, another file set it first. If you use Match blocks, add the connection you care about so they are applied too:
sudo sshd -T -C user=you,host=your.server.example,addr=203.0.113.10
Reload or restart
On Ubuntu and Debian, the ssh unit runs sshd -t before a reload and before a start. The difference is what happens when that check fails:
sudo systemctl reload ssh
- Reload sends sshd a hangup signal and it rereads its config. If the check fails, the reload fails and the old sshd keeps running with the old settings. New logins still work.
- Restart stops sshd first. If the check then fails, sshd doesn't come back and nobody can open a new connection.
Either way, existing sessions stay up: the unit uses KillMode=process, so only the listening daemon is stopped, not your shell. Prefer reload for config changes. Ubuntu's docs use restart, which is fine once sshd -t passes.
Don't close your old session yet. It survives the reload, so it looks like everything worked. Until a fresh login succeeds, that open session is your only way to fix the config without the provider console.
Test with a fresh connection
From a new terminal on your Mac, connect as you normally would:
ssh -v [email protected]
Look for Authenticated and the publickey method in the verbose output. Then confirm that the things you blocked are blocked:
# should fail with: Permission denied (publickey)
ssh -o PubkeyAuthentication=no [email protected]
# should fail
ssh [email protected]
Only when the good login works and the bad ones fail, close the old session.
If you get locked out
If the fresh login fails and your old session is still open, move the drop-in out of the way and reload:
sudo mv /etc/ssh/sshd_config.d/00-hardening.conf /root/
sudo sshd -t && sudo systemctl reload ssh
If you already closed it, use your provider's web or serial console, log in as a user with a password, and do the same. Some providers can also boot a rescue system where you mount the disk and edit the file. The server's auth log usually says why a login was refused:
sudo journalctl -u ssh -n 50
After SSH is locked down, the next steps are a firewall that leaves your SSH port open (set up UFW safely) and banning repeat offenders (fail2ban for SSH).
Stenvakt applies this without the risk. Its Review shows which of these settings your servers are missing, and Preview shows every line it will change before it runs. Fixes run in safe mode with a snapshot, a rollback timer and a fresh-connection test: if a change cuts off SSH, the server rolls it back by itself. Shared-SSH protection makes sure it never locks out other people's logins. It's a Mac app and isn't out yet: get early access.
Sources
- OpenBSD manual: sshd_config(5)
- OpenBSD manual: sshd(8) (-t, -T, -C and SIGHUP)
- Ubuntu Server docs: OpenSSH server
- Ubuntu openssh package (noble): ssh.service unit