You need a Linux machine where you can use sudo, and
you should not practise on a server anyone depends on. The commands
here change real system state. A spare machine, a virtual machine, or a
cloud instance you can rebuild is the right place; a laptop you own is
fine too. If the terminal itself is new to you, do
Introduction to the
Linux Terminal first — it takes about ten minutes and everything
below assumes it. Every step tells you how to undo it.
The Front Door
SSH is how you administer a Linux server, which makes it the single most valuable target on the machine. Any host with a public IP receives automated password-guessing attempts continuously — not because anyone is interested in you specifically, but because scanning the entire internet is cheap.
The good news is that hardening SSH is mostly a matter of turning off things you do not use. Two changes — key-only authentication and no root login — remove essentially the whole automated threat.
Every change below is made over the connection it modifies. Open a second SSH session before you start and leave it connected. Test the new configuration in a third session. If you lock yourself out, that still-open session is the difference between a quick fix and a trip to a rescue console.
Step 1: Key-Based Authentication
Generate a key on your client machine, not the server:
ssh-keygen -t ed25519 -C "alice@laptop"
Ed25519 is the modern default: short, fast, and strong. Use RSA only if you must talk to
something ancient, and then -t rsa -b 4096. Always set a passphrase — it is what
protects the key if the laptop is stolen.
Copy the public key to the server:
ssh-copy-id alice@server
This appends to ~/.ssh/authorized_keys with the right permissions. Doing it by
hand is fine too, but the permissions are strict and OpenSSH will silently refuse a key if
they are wrong:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Confirm the key works before disabling passwords. Open a new session and log in. If it still asks for your password, the key is not being accepted — fix that first.
Step 2: The Configuration
The server configuration is /etc/ssh/sshd_config. On current Debian and Ubuntu
it ends with an Include /etc/ssh/sshd_config.d/*.conf line, and putting your
changes in a drop-in file there is cleaner — it survives package upgrades instead of
producing merge prompts.
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
X11Forwarding no
MaxAuthTries 3
LoginGraceTime 30
AllowUsers alice bob
PermitRootLogin no— attackers knowrootexists. Log in as yourself and usesudoPasswordAuthentication no— the single highest-value line. It ends password guessing entirelyKbdInteractiveAuthentication no— closes the other interactive path, which is easy to forget and leaves passwords usableAllowUsers— an explicit allowlist. Anyone not named cannot authenticate at allMaxAuthTries— attempts per connection
sshd_config.
Unlike most config files, OpenSSH uses the first occurrence of a keyword and
ignores later ones. A setting already present earlier in the main file will beat your
drop-in unless the Include comes first — check the top of
sshd_config to see where the include sits.
Step 3: Test Before Reloading
sudo sshd -t # syntax check — silent means valid
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauth|allowusers'
sshd -T prints the effective configuration after all includes and
matches are resolved. This is the authoritative answer to "is my setting actually active?",
and it is worth trusting over reading the files.
sudo systemctl reload ssh
Use reload, not restart — existing sessions survive. Now open a
new session and confirm you can still get in.
The Socket-Activation Gotcha
Port in sshd_config does nothing.
Since Ubuntu 22.10, ssh is socket-activated: systemd owns the listening
port, not sshd. The Port directive is ignored. Change it in the socket
instead:
sudo systemctl edit ssh.socket
and set ListenStream= (empty, to clear the default) followed by
ListenStream=2222, then
sudo systemctl daemon-reload && sudo systemctl restart ssh.socket.
Check which applies to you with systemctl is-enabled ssh.socket.
Changing the port is worth keeping in perspective: it dramatically reduces log noise from automated scanners, but it is not a security control. A targeted attacker finds the new port in seconds. Do it for quieter logs, not for protection.
Beyond the Basics
- Restrict what a key can do — prefix an entry in
authorized_keyswith options such asrestrict,command="/usr/bin/backup.sh",from="203.0.113.5". Ideal for automation keys that should do exactly one thing - Use a hardware security key —
ssh-keygen -t ed25519-skcreates a key that requires a physical touch and cannot be copied off the device - Prefer an SSH agent with confirmation —
ssh-add -cprompts on every use, so a compromised client cannot use your key silently - Avoid agent forwarding to untrusted hosts; root there can use your agent.
ProxyJumpis the safer way to reach a machine through a bastion - Check the host key on first connect — that fingerprint prompt is your only defence against a man-in-the-middle, and clicking through it defeats the point
Verifying It Worked
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no alice@server
This should be refused. If it prompts for a password, password authentication is still
enabled somewhere — go back to sshd -T and find where.
sudo journalctl -u ssh --since "1 hour ago" | tail -30
sudo lastb | head # failed login attempts
Now Do It Yourself: Five Steps
You will create a key pair, understand why its permissions matter, install it on a server, and turn off password logins — without locking yourself out, which is the way this job usually goes wrong. Steps 1 to 3 need nothing but the machine in front of you; steps 4 and 5 need a server you can afford to break.
Go: open a terminal on your own computer — the one you will connect from, never the server.
Do: run this, replacing the comment with something that identifies the machine. When it asks for a passphrase, type a real one.
ssh-keygen -t ed25519 -C "alice@laptop"
You should see: prompts for a file location and a passphrase, then a fingerprint and a piece of ASCII art. Press Enter to accept the default location. Choose ed25519 over RSA: the keys are far shorter, faster, and every current OpenSSH supports them.
If not: ssh-keygen: command not found means the OpenSSH client is not installed — on Debian and Ubuntu that is sudo apt install openssh-client. 🔴 The passphrase is not optional in practice. Without one, anyone who copies that single file becomes you on every server that trusts it. With one, they also need the passphrase, and ssh-agent means you still only type it once per session.
Go: same terminal.
Do: run these two commands.
ls -l ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub
ssh-keygen -lf ~/.ssh/id_ed25519.pub
You should see: two files — the private key at mode 600, and the public one readable more widely. Then a line like 256 SHA256:mf964P6/G6gQ5bggUiCQKgOHhzOJdG5SCd6tDpk88lw alice@laptop (ED25519). The rule is simple and absolute: the file without .pub never leaves your machine; the one with .pub is meant to be handed out freely.
If not: if the private key is anything other than 600, fix it now with chmod 600 ~/.ssh/id_ed25519. OpenSSH refuses to use a private key that others can read, with UNPROTECTED PRIVATE KEY FILE, and that refusal is a feature. The directory matters too: chmod 700 ~/.ssh.
Go: still on your own machine. You need the server's address and a password login that currently works.
Do: run ssh-copy-id alice@your-server, then log in again with ssh alice@your-server.
You should see: the tool reporting the number of keys added, then a normal login — asking for your key passphrase rather than your account password, or nothing at all if the agent already holds it. On the server the key now sits in ~/.ssh/authorized_keys, one key per line.
If not: if it still asks for your account password, the server did not accept the key. The cause is almost always permissions on the server side: ~/.ssh must be 700 and authorized_keys 600, and the home directory itself must not be group-writable — sshd silently ignores keys in a directory anyone else could alter. Diagnose with ssh -v alice@your-server and read the lines mentioning publickey.
Go: log in to the server. 🔴 Open a SECOND terminal and log in again, and leave it open. That spare session is what saves you if the next step goes wrong; it stays authenticated even after sshd restarts.
Do: in the first session, edit the server's sshd configuration with sudo nano /etc/ssh/sshd_config and set these three lines, then validate and reload.
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
You should see: sudo sshd -t printing nothing at all — silence means the file is valid. Only then run sudo systemctl reload sshd. Now, from a third terminal, prove a fresh login still works before you close anything.
If not: if the new login fails, use the second session you kept open, undo the change, and reload again. This step could not be executed while writing this page — there is no SSH server on the machine it was written on — so treat these as the standard directives rather than captured output, and rely on sshd -t and your spare session, which are the checks that actually protect you. ⚠️ Never test by closing every session and hoping.
Go: on the server, open ~/.ssh/authorized_keys.
Do: put options in front of a key, on the same line, separated by commas.
from="203.0.113.5",no-agent-forwarding,no-port-forwarding ssh-ed25519 AAAA...
You should see: that key now works only from that address, and cannot be used to forward ports or your agent. Each key in the file can carry different restrictions, which is what makes per-key limits practical: a deployment key can be locked to one source address and refused everything else.
If not: if the key stops working entirely, the options are almost certainly on their own line — they must be on the same line as the key, before it, with no line break. Also add command="..." if a key should only ever run one specific thing, which is the right shape for automated backups and deploys.
Without scrolling up: before reloading sshd after a configuration change, what two
things must you have done? Answer: run sudo sshd -t and seen it print
nothing, and kept a second authenticated session open. The first catches a broken file;
the second saves you when the file is valid but the policy locks you out.
Now do it without the page: generate a second key labelled for a
backup job, and write the authorized_keys line that would let it run one
named command and nothing else, from one address. You need command= and
from= together — step 5's shape, tightened.
Summary
- Keep a second session open — every change is made over the thing you are changing
- Keys first, then disable passwords — confirm the key works before you close the door
- Disable root login and keyboard-interactive, and use
AllowUsers sshd -tthensshd -T— validate, then confirm the effective config- First match wins in
sshd_config - Port changes need the socket on Ubuntu 22.10+, and buy quiet logs rather than security
PasswordAuthentication no and PermitRootLogin no. Together
they make the continuous automated guessing against your server irrelevant — everything
else on this page is refinement.