SSH hardening and fail2ban without locking yourself out
The first 15 minutes post gets you to a working baseline: a normal user, keys, and password login off. This one goes a level deeper on SSH itself. It covers how to do the switch without locking yourself out, a few extra settings worth having, and when fail2ban is worth installing.
Everything here is done as your normal user with sudo, on Debian or Ubuntu.
1. Before you touch anything
Do these checks first, so a mistake is recoverable:
- Open two SSH sessions. Do the editing in one and keep the other untouched as your way back in.
- Know where your provider's emergency console is. On netcup that's the web console in the server control panel (SCP); the netcup SCP guide shows where to find it. It works even if SSH is completely broken.
- Check that you can use
sudowith your normal user:sudo -v. If that fails, fix it before disabling root login.
2. Create the user and add your key
sudo adduser deploy sudo usermod -aG sudo deploy
From your own machine, not the server:
ssh-keygen -t ed25519 ssh-copy-id deploy@YOUR_SERVER_IP ssh deploy@YOUR_SERVER_IP
Protect the private key with a passphrase when ssh-keygen asks. If someone copies the file off your laptop, the passphrase is all that's left between them and your server. An agent such as ssh-agent means you only type it once per session.
The key must work before you go any further. Log in with it, in a new terminal, and see that it doesn't ask for the server password. It may ask for the key passphrase, which is fine.
3. Disable password and root login
Use a drop-in file instead of editing the main config:
sudo nano /etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes MaxAuthTries 3 LoginGraceTime 30 AllowUsers deploy
AllowUsers is a whitelist: only the listed accounts can log in over SSH at all. Make sure the name is the user you just tested, or this is how you lock yourself out.
One catch: sshd uses the first value it finds for each setting, and files in sshd_config.d are read in alphabetical order. Some cloud images ship a file like 50-cloud-init.conf that sets PasswordAuthentication yes. Because 10- sorts before 50-, yours wins, but check anyway:
sudo sshd -T | grep -Ei 'passwordauthentication|permitrootlogin|allowusers'
sshd -T prints the settings sshd will really use after merging every file. If it says passwordauthentication no, you're good.
4. Test, then reload
sudo sshd -t && sudo systemctl reload ssh
sshd -t checks the syntax. If it prints anything, fix that before reloading. A reload doesn't kill your open sessions, which is exactly why the second one is your safety net. On some distributions the service is called sshd instead of ssh.
Now, from a third terminal, log in again as deploy. Then try the two things that should now fail:
ssh root@YOUR_SERVER_IP ssh -o PubkeyAuthentication=no deploy@YOUR_SERVER_IP
Both should end in "Permission denied". Only once the new login works and the old ways are shut do you close your spare sessions.
5. If you do lock yourself out
It happens. Don't panic and don't reinstall:
- Open the web console from the provider's control panel. It's a virtual screen, so it doesn't use SSH.
- Log in with the user's password. This is why you don't want to delete the password with
passwd -d; passwords still work on the console even when SSH refuses them. - Fix the file in
/etc/ssh/sshd_config.d/, runsudo sshd -t, reload, and test again from a second terminal.
If you're locked out because of the firewall rather than SSH, the same console fixes it: sudo ufw allow 22/tcp. Always allow SSH before running ufw enable. The firewall generator builds the rules in the right order if you want a starting point.
6. fail2ban
With passwords off, nobody is going to guess their way in. What fail2ban still buys you is quieter logs and less wasted CPU from bots hammering the login prompt. It watches the log and temporarily bans IPs that keep failing.
sudo apt install fail2ban
Don't edit jail.conf, as package updates can overwrite it. Create your own file:
sudo nano /etc/fail2ban/jail.d/sshd.local
[sshd] enabled = true maxretry = 5 findtime = 10m bantime = 1h ignoreip = 127.0.0.1/8 ::1
sudo systemctl enable --now fail2ban sudo fail2ban-client status sshd
If you have a fixed home or office IP, add it to ignoreip (separated by a space) so you can't ban yourself. If your IP changes, leave it out and remember that a ban is temporary: restarting fail2ban clears it, or unban a specific address with sudo fail2ban-client set sshd unbanip ADDRESS.
On a current Debian or Ubuntu, sshd logs to the systemd journal, and fail2ban's default settings on those systems generally cope with that. The status command above is your check: if the jail shows up and the "Currently failed" counter moves over time, it's reading the right place.
7. Two habits that matter more than any setting
- Keep your server updated. An SSH daemon that's patched beats an exotic config. Turn on
unattended-upgradesas in the first post. - Don't expose things you don't need. The best way to keep a service safe from the internet is to not publish it. For admin panels, put them behind a WireGuard tunnel. You can then even restrict SSH to the tunnel address.
When to go further
Moving SSH off port 22 and port knocking mostly reduce noise, not risk, so I skip them. If you manage several servers or other people, look at SSH certificates or a bastion host. For one person and one VPS, keys plus a patched system plus a closed firewall is what I'd stop at. Next, get your backups running.