Tutorials

SSH: Key-Only Login and Sensible Hardening

Turn off password authentication, lock down root, and stop the log noise — the twenty minutes that closes the most-attacked door on any server.

beginner ⏱ 25 min Be Your Own Cloud · Chapter 5 The Privacy Diet · Chapter 9 sshlinuxhardening

· last updated 29 October 2026

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. Chapter 5 of Be Your Own Cloud covers the essentials in three lines of sshd_config; this page goes further, and adds the check that tells you those lines actually took effect.

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/10-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.

The 10- matters. sshd reads the files in sshd_config.d/ in name order, before the rest of sshd_config, and for each setting the first value it reads wins. Ubuntu’s installer often leaves a 50-cloud-init.conf there containing PasswordAuthentication yes. A file named 99-… is read after it and loses; a line in sshd_config itself loses too. Starting the name with 10- puts yours first. See what’s there:

ls /etc/ssh/sshd_config.d/
# 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]

The Privacy Diet’s 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

Then ask sshd which values it’s actually using — this is the check that catches a setting overridden by another file:

sudo sshd -T | grep -Ei '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|pubkeyauthentication)'

You want passwordauthentication no, kbdinteractiveauthentication no, permitrootlogin no and pubkeyauthentication yes. If you followed Chapter 5 and edited sshd_config itself, run the same check — it’s how you’d find out a drop-in file is quietly overriding you.

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 and open it in ufw before restarting. Since Ubuntu 22.10, SSH is started by a systemd socket rather than listening on its own, so restarting ssh alone won’t move the port — Ubuntu’s packaging documents this step instead:

sudo ufw allow from 192.168.1.0/24 to any port 2222 proto tcp comment 'SSH from LAN only'
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
sudo ss -tlnp | grep ssh      # should now show :2222

Then update your ~/.ssh/config, and remove the old port 22 rule once the new port works.

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.

Stuck on a step? Get in touch.