Before you start
- A Linux server reachable over SSH, with an account that has
sudorights - A terminal on your computer: Terminal (macOS, Linux) or PowerShell (Windows 10 and 11)
- A fallback console (screen and keyboard, hypervisor or hosting provider console) in case something goes wrong
In plain words
A password is a door code that anyone can try to guess, thousands of times a day. An SSH key is a real physical key: the server keeps the lock (the public key), you keep the key (the private key) in your pocket. You fit the new lock, check that the key turns, and only then do you brick up the old coded door.
A server exposed over SSH constantly receives automated login attempts trying thousands of user name and password pairs. The most effective defence: accept SSH keys only, and forbid direct root login.
This tutorial follows a precise order so you never lock yourself out: you install and test the key before disabling passwords.
1. Create a key pair on your computer
On your computer (not on the server):
ssh-keygen -t ed25519 -C "spo@office-pc"
- Accept the suggested location (
~/.ssh/id_ed25519). - Choose a passphrase: it protects the key if your computer is stolen. The SSH agent remembers it for the session.
Two files are created:
| File | Role | Share it? |
|---|---|---|
id_ed25519 |
Private key | Never, it does not leave your computer |
id_ed25519.pub |
Public key | Yes, this is the one you copy to servers |
2. Install the public key on the server
Linux and macOS:
ssh-copy-id spo@192.168.1.50
Windows (PowerShell), where ssh-copy-id does not exist:
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | ssh spo@192.168.1.50 "umask 077; mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
Replace spo with your user and the address with the server's.
3. Test key-based login
ssh spo@192.168.1.50
The login must succeed without asking for the account password (only the key's passphrase, if you set one).
Warning
Do not move on to the next step if this test fails. Check the permissions on the server: chmod 700 ~/.ssh and chmod 600 ~/.ssh/authorized_keys; the home folder must not be writable by other users.
4. Harden the server configuration
Rather than editing the main file /etc/ssh/sshd_config, create a dedicated file in /etc/ssh/sshd_config.d/. It survives package updates and is easy to find.
sudo nano /etc/ssh/sshd_config.d/00-hardening.conf
Contents:
# Key-based authentication only
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
# No direct root login: log in, then use sudo
PermitRootLogin no
# Limit attempts and login time
MaxAuthTries 3
LoginGraceTime 30
# Features a server does not need
X11Forwarding no
# Only these accounts may log in (separated by spaces)
AllowUsers spo
Why the "00-" prefix?
For OpenSSH, the first value read wins. Files in sshd_config.d/ are read in alphabetical order, before the rest of sshd_config. A file such as 50-cloud-init.conf, created by some cloud images, can turn PasswordAuthentication yes back on: the 00- prefix makes sure your settings come first.
5. Check, then apply, without closing your session
Check the syntax. The command must print nothing:
sudo sshd -t
Check the configuration actually in effect:
sudo sshd -T | grep -Ei '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|allowusers)'
Expected result:
permitrootlogin no
passwordauthentication no
kbdinteractiveauthentication no
allowusers spo
Reload the service:
sudo systemctl reload ssh # Debian, Ubuntu
sudo systemctl reload sshd # Fedora, RHEL, Rocky, Alma, Arch
Danger
Keep your current session open. Open a second terminal and test a new login. If it fails, fix things from the first session, which is still connected.
6. Check that passwords are really refused
From your computer, force a password attempt:
ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password spo@192.168.1.50
Expected answer: Permission denied (publickey). The server no longer even offers password login.
Going further
- Firewall: only allow port 22 from your management networks or your VPN. That beats every other measure.
- Fail2ban or CrowdSec: temporarily ban addresses that pile up failures. Useful on a server exposed to the internet, less essential once passwords are disabled.
- Changing the port: reduces noise in the logs, but brings no real security. On Ubuntu 24.04 and later, SSH is started by
ssh.socket: after changingPort, runsudo systemctl daemon-reloadthensudo systemctl restart ssh.socket, otherwise the old port keeps listening. - Logs:
sudo journalctl -u ssh --since today(or-u sshd) shows logins and failures.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Permission denied (publickey) with your key |
Wrong permissions on ~/.ssh, or key missing from authorized_keys |
Fix the permissions (step 3), check the file's contents |
| Passwords are still accepted | Another file in sshd_config.d/ is read before yours |
Check with sudo sshd -T, rename your file with a lower prefix |
| No access at all | Configuration error and session closed | Log in through the fallback console, delete 00-hardening.conf, reload the service |
Sources
- OpenBSD: sshd_config(5), the first value obtained wins
- OpenBSD: ssh-keygen(1)
- Ubuntu Server: OpenSSH server, sshd_config.d files
- Launchpad: changing the port with ssh.socket requires daemon-reload (bug 2069041)
- Microsoft Learn: OpenSSH key management on Windows
- ANSSI (French cybersecurity agency): (Open)SSH secure use recommendations (PDF)