Every few months someone asks me for "the SSH hardening checklist," expecting a short list of three settings. The honest answer is that most of the impact comes from about six changes, and the rest is diminishing returns dressed up as security theater. This is the version I actually run on every server I manage, client or personal.

The baseline most guides skip

Before touching any config file, two things matter more than anything in sshd_config: whether the server is reachable from the entire internet, and whether someone other than you can guess valid usernames. If you can put SSH behind a VPN like Tailscale or WireGuard, do that first — everything below becomes a second layer of defense rather than the only one.

Assuming the server is internet-facing (which, fairly often, it has to be), here's what consistently matters:

  • Disable password authentication entirely — key-based auth only.
  • Disable root login over SSH.
  • Change the default port — not for "security," but to cut scanning-bot noise in your logs by roughly 95%.
  • Limit which users can connect via SSH at all, with AllowUsers.
  • Keep the SSH daemon itself patched — this matters more than any config tweak.
!

Changing the port doesn't stop a targeted attacker — it stops the automated noise. Don't treat it as a security control on its own, and don't skip the rest of this list because you did it.

Key types that still hold up

RSA at 2048 bits is no longer something I'd generate new, though existing keys at 4096 bits are still fine for the next several years. For new keys, Ed25519 is the better default: smaller, faster to verify, and resistant to the timing-attack classes that occasionally surface for RSA implementations.

Generating a new key

ssh-keygen -t ed25519 -C "mikko@viima-2026" -a 100

The -a 100 flag increases the KDF rounds for the private key's passphrase, which slows down brute-force attempts if the key file itself is ever stolen. It adds a fraction of a second to your own login and meaningfully more to anyone trying to crack an offline copy.

Adding MFA without breaking automation

The part people get wrong with SSH MFA is applying it uniformly and then discovering their deploy scripts and monitoring agents can't authenticate anymore. The fix is to scope it by user or group rather than globally.

/etc/ssh/sshd_config — MFA scoped to interactive users

Match Group interactive-users
    AuthenticationMethods publickey,keyboard-interactive
    KbdInteractiveAuthentication yes

Service accounts and deploy keys stay on publickey-only auth, while anyone in the interactive-users group needs both a key and a TOTP code via PAM and Google Authenticator's libpam-google-authenticator, or an equivalent. I've used Duo for clients who already pay for it, but for personal infrastructure, TOTP plus a password manager covers it without a subscription.

A full sshd_config worth copying

This is close to what I deploy by default, trimmed of client-specific bits:

/etc/ssh/sshd_config

Port 2222
Protocol 2
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers mikko deploy
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
LogLevel VERBOSE

AllowTcpForwarding no is the one people forget — without it, anyone with a valid login can tunnel arbitrary traffic through your server, which matters more than it sounds like it would on a shared or multi-tenant box.

Where fail2ban still earns its place

With key-only auth, fail2ban isn't stopping anyone from logging in — they couldn't anyway without the key. What it does well is keep your logs readable and your connection table clean by banning IPs that hammer the port with auth attempts. On a busy server that's worth doing on its own.

/etc/fail2ban/jail.local

[sshd]
enabled = true
port = 2222
maxretry = 4
bantime = 1h
findtime = 10m

That's the whole list, more or less. None of it is exotic, and that's somewhat the point — the goal isn't to find a clever trick, it's to remove the easy wins an attacker is hoping you skipped.

ML

Mikko Laaksonen

DevOps engineer based in Tampere, Finland. Writes about Linux, Docker, databases, and web design.

More about Mikko →