SSH Hardening and Key-Based Authentication¶
Every internet-facing Linux server gets scanned for SSH logins within minutes of being connected. This article covers moving from password to key-based authentication and the sshd_config changes that meaningfully reduce exposure.
How it works¶
Generating and deploying a key pair¶
ssh-keygen -t ed25519 -C "[email protected]"
-t ed25519 selects the Ed25519 algorithm — smaller keys and faster verification than RSA, with equivalent or better real-world security; prefer it unless you need to support a genuinely ancient client/server that predates it (RSA with a 4096-bit key, -t rsa -b 4096, is the fallback). This creates a private key (~/.ssh/id_ed25519, never share this) and a public key (~/.ssh/id_ed25519.pub, safe to share — this is what goes on servers).
This appends your public key to ~/.ssh/authorized_keys on the remote server (creating the file and directory with correct permissions if needed). Equivalent manually:
cat ~/.ssh/id_ed25519.pub | ssh user@server 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'
Permissions matter: sshd silently refuses to use an authorized_keys file (or .ssh directory) that's group- or world-writable, as a security measure against other local users tampering with it. 700 for ~/.ssh, 600 for authorized_keys and the private key itself.
Hardening sshd_config¶
Key settings in /etc/ssh/sshd_config (confirm your OpenSSH version supports each — these have been stable for many years, ssh -V shows your version):
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
MaxAuthTries 3
AllowUsers deploy admin
PasswordAuthentication no— only after confirming key-based login actually works, in a separate session, before closing your current one. This is the single most important hardening step: it makes brute-force password guessing against SSH entirely moot, since there's no password to guess.PermitRootLogin no— forces even legitimate admins to log in as a named user andsudo, which also means every privileged action is attributable to a specific account in logs.MaxAuthTries 3— disconnects after this many failed attempts within a single connection (not a long-term ban — see fail2ban below for that). Caution: every key your SSH agent offers counts as one attempt, so with several keys loaded a low value can disconnect you before the right key is even tried — pin the key withssh -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 user@server.AllowUsers— an explicit allowlist of which local accounts may SSH in at all, regardless of whether they have valid credentials. A typo or a missing name here locks that account out of SSH entirely, including yours.
After any sshd_config change:
sudo sshd -t # syntax-check BEFORE restarting — catches typos safely
sudo systemctl restart sshd # Debian/Ubuntu: the unit is ssh.service (the sshd alias usually works too)
sshd -t validates the config without affecting the running daemon — always run this before restarting, since a syntax error combined with a restart can leave you with no running SSH daemon at all and no way back in remotely.
Also check the effective configuration, not just the file you edited:
OpenSSH uses the first value it finds for each keyword, and distributions include drop-in files from /etc/ssh/sshd_config.d/*.conf near the top of sshd_config. A cloud image's 50-cloud-init.conf containing PasswordAuthentication yes therefore silently overrides your PasswordAuthentication no further down the main file.
Rate-limiting repeated failures with fail2ban¶
fail2ban watches the auth log, and after maxretry failed attempts from the same source within findtime, inserts a firewall rule banning that source IP for bantime. This is a different, complementary layer to sshd_config's MaxAuthTries — the latter limits attempts per connection, fail2ban limits attempts (and distinct connections) per source IP over time.
Ubuntu/Debian vs. RHEL-family differences¶
| Ubuntu/Debian | RHEL-family | |
|---|---|---|
| Service name | ssh (unit ssh.service) | sshd (unit sshd.service) |
| SELinux | Not enforcing by default | Enforcing — a key/file in a non-standard location may need restorecon to get the right SELinux context before sshd will use it |
Practical exercise¶
- Generate an Ed25519 key pair and deploy the public key to a test VM.
- Confirm key-based login works, in a new terminal session, without closing your current one.
- Only after confirming step 2 works: set
PasswordAuthentication no, runsshd -t, then restart, then open yet another new session to confirm password login is now refused.
How to verify you did it correctly¶
ssh -v user@server(verbose) should showAuthentication succeeded (publickey)and never prompt for a password.- Attempting
ssh -o PreferredAuthentications=password user@serverafter the hardening should be refused outright, not even prompt for a password. sudo sshd -T | grep -i passwordauthenticationshould printpasswordauthentication no. If it printsyesalthough your file saysno, a drop-in under/etc/ssh/sshd_config.d/is overriding it.
Common mistakes¶
- Disabling password authentication before confirming key-based login works. If your only active session closes and the key doesn't actually work, you're locked out — always test in a second, independent session first.
- Copying a private key instead of the public key to the server (the file without
.pub). This doesn't grant access and typically also leaves your private key sitting insecurely on a remote host. - Forgetting
sshd -tbefore restarting after an edit — a typo insshd_configcan prevent the daemon from starting at all, and you find out only after it's already down.
⚠ Dangerous commands¶
- Restarting
sshd/ssh.serviceimmediately after an untested config change, on your only active remote connection, with no console/out-of-band access as a fallback. Always keep an existing session open and verify the new config in a fresh one first. chmod -Ron~/.ssh(recursive) can change the private key's permissions to something sshd refuses — set permissions on the directory and specific files explicitly instead.
Related articles¶
Review questions¶
- Why must you test key-based login in a second session before disabling password authentication?
- What's the difference between what
MaxAuthTriesinsshd_configlimits and what fail2ban limits? - Why does sshd refuse to use an
authorized_keysfile that's group-writable? - What does
sshd -tactually check, and why run it before every restart?