Every server with port 22 reachable gets scanned. Not targeted — scanned, constantly, by bots working through credential lists. Password authentication is the only thing that makes those attempts worth making. Take it away and the entire category of attack stops mattering.
This assumes you already have a server you can SSH into.
Get a key onto the server first
Do this before touching any config. Locking out password login while your key doesn’t work means a keyboard, a monitor and a trip to wherever the machine lives.
On your client machine:
ssh-keygen -t ed25519 -C "laptop -> homelab"
Ed25519 over RSA: shorter keys, faster, and no parameter choices to get wrong. Use a passphrase — it encrypts the private key at rest, so a stolen laptop isn’t a stolen server. Your agent will cache it, so you type it once per session, not once per connection.
Copy it across:
ssh-copy-id [email protected]
Now prove the key works before you change anything:
ssh -o PasswordAuthentication=no [email protected]
If that drops you at a prompt, you’re safe to continue. If it asks for a password, the key isn’t in place — fix that first.
Keep a second session open
The rule that saves people: open a second SSH session and leave it connected while you edit the config. An existing session survives an sshd restart. If you break the config, that window is how you fix it instead of driving to the machine.
The config
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
Use a drop-in file rather than editing sshd_config directly — package upgrades leave it alone, and everything you changed is in one readable place.
# Key-only. The reason we're here.
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
# No direct root login, by key or otherwise. Log in as yourself, then sudo —
# so the logs record which human did what.
PermitRootLogin no
# Don't let a misconfigured client fall back to something weaker.
PermitEmptyPasswords no
AuthenticationMethods publickey
# Limit who may log in at all.
AllowUsers yourname
# Drop unauthenticated connections quickly; don't let them pile up.
LoginGraceTime 20
MaxAuthTries 3
MaxSessions 4
# Turn off features a home server doesn't use.
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding yes # keep: needed for `ssh -L` tunnels to admin UIs
That last one is worth a moment. Local port forwarding is how you reach a service bound to 127.0.0.1 on the server without exposing it to the network:
ssh -L 8080:localhost:8080 [email protected]
Chapter 9 makes the point that Pi-hole has no reason to be externally accessible. An SSH tunnel is how you administer something that isn’t listening anywhere but loopback.
Check, then apply
sshd validates its own config. Always use this before restarting:
sudo sshd -t && echo "config OK"
Only if that prints config OK:
sudo systemctl restart ssh
Now, from a new terminal and without closing the one you already have open:
ssh [email protected]
Works? Done. You can close the safety session.
Quiet the logs with fail2ban
With passwords off, brute-force attempts can’t succeed — but they still fill your logs and waste cycles. fail2ban watches for repeated failures and firewall-bans the source.
sudo apt install -y fail2ban
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 3
backend = systemd
# Never lock yourself out from the LAN
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24
[sshd]
enabled = true
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
On changing the port
Moving SSH to a high port is often recommended. Be clear about what it buys: it removes almost all undirected scan noise, and it provides no security against anyone who runs nmap against you. It’s log hygiene, not a control. If you already restricted port 22 to your LAN in ufw, you have the real protection and the noise is already gone.
If you want it anyway, add Port 2222 to the drop-in, open it in ufw before restarting, and update your ~/.ssh/config.
Make the client side pleasant
On your laptop, ~/.ssh/config:
Host homelab
HostName 192.168.1.50
User yourname
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ServerAliveInterval 60
Now it’s just ssh homelab. IdentitiesOnly yes matters once you have several keys — without it your client offers each one in turn and can trip MaxAuthTries before reaching the right one.
Where this leaves you
Password authentication is off, root can’t log in directly, only your user is permitted, and repeat offenders get banned. Next: Docker and Compose.