SSH hardening: the configuration that holds up in daily work
How we harden SSH on inherited servers in one afternoon: key-only access, deliberate algorithm choices, rate limits, and session hygiene.
SSH is the one door on most Linux servers that stays open around the clock, which makes it worth concentrating on. When we harden SSH access, we remove an entire class of risk in one pass and rarely hear about the topic again. This post is the sequence we run through on inherited servers in a single afternoon: from key-only setup through algorithm choices to the question of which distribution defaults we simply leave alone.
Sort out the access first, close the door second
The first step is bookkeeping, not a config key. A server that has run for a few years almost always carries host keys from older generations, authorized_keys entries nobody can assign anymore, and accounts that were abandoned long ago. Before touching sshd_config, we list every path into the system: which keys are valid, which accounts they belong to, and who still actually needs them. Anything that cannot be mapped to a living person or role gets removed. Beyond cleaning up, this produces the basis for everything else: hardening that does not know who is supposed to log in ends up locking out the people it was protecting.
Keys only
The single most valuable measure remains disabling password login: PasswordAuthentication no in the server configuration, applied after opening a second terminal that stays open until key-based login is proven. The root question comes with it: with PermitRootLogin prohibit-password, direct root access still works but only with keys; in most teams the cleaner path is dedicated user accounts with sudo. We do not force PubkeyAuthentication itself, it has been the default for years; what matters is that the opposite direction is reliably closed.
One piece is easy to overlook: the client participates too. An ssh agent holding the keys keeps private material off questionable laptops, and ssh -v shows exactly which key was offered and why the server declined it. That diagnostic output is our best friend during a migration, and it costs nothing.
Algorithms with standards, not with exclusivity
Modern OpenSSH versions ship defaults that are well chosen for daily work: Curve25519 for key exchange, Ed25519 for host and user keys, ChaCha20-Poly1305 and AES-GCM for encryption. In most cases the right move is to verify the defaults and document them explicitly, not to invent our own curve. With KexAlgorithms, Ciphers and MACs we pin the broad, modern set deliberately and leave exotic variants out. Filtering too aggressively breaks older clients, and an access path that fails during an emergency is not hardening, it is an incident with advance notice. We treat DSA separately and simply refuse it: new keys get Ed25519, RSA stays at 3072 bits and above only where a system requires it.
Throttle, bound, log
Even with keys only, the port stays exposed and the login surface deserves damping. The parameters live in sshd_config: MaxAuthTries bounds the attempts per connection, LoginGraceTime keeps unauthenticated sessions from lingering forever, and AllowUsers or better AllowGroups decouples the set of possible logins from the account list. MaxSessions and MaxStartups push back against connection floods. We do not use port knocking, but we do apply rate limiting at the network level plus a two-tier alarm: the raw count of failed attempts is normally high and harmless; a rise above the usual level is the signal worth reacting to.
Session hygiene completes the picture: sessions should end. ClientAliveInterval with a matching ClientAliveCountMax disconnects sleeping connections, and agent forwarding stays a per-host decision in the client configuration, never a global one.
What the distribution already gets right
A fair share of the homework ships done, and it pays to check before configuring ourselves. Current Debian releases run sshd socket-activated and ship a configuration line that already sorts out weak algorithms. The release notes name such changes explicitly, and reading them saves duplicated work. Whatever we add goes into a drop-in file under /etc/ssh/sshd_config.d/, so a package update leaves our local decisions untouched. Then follows the mandatory step of every reconfiguration: sshd -t validates the syntax, and the restart happens only after a second parallel session has confirmed the access still holds.
The second keyring: the break-glass path
Next to the hardened main door sits a smaller topic: the emergency path. Every machine needs a way in that still works when the keyring file gets lost or an automation misses the right rotation. Our standard is a reserve key with a provenance separate from the everyday machines, stored in a considered location and usable only with documented responsibility. The frequency rule matters as much as the design: a break-glass key that is never touched rusts, so it gets exercised deliberately in a half-year cycle and then re-hardened, proving the procedure works while it matters.
The second part of this discipline is the inventory on the client side. Keys travel with people, devices get replaced, and a keyring inherited from a decommissioned laptop is a liability with a long memory. So every hardening round ends with a short reconciliation: who holds the current keys, which devices are involved, which entries have become surplus. Five minutes per cycle, and the next hardening phase starts from a clean board.
At the end of the afternoon the server rejects passwords, permits known groups only, negotiates modern algorithms, and keeps counters on its attempts. Nothing spectacular, and that is the point: the door is closed in a way that keeps the keyring working. If you want us to put this baseline in place across your fleet or review your existing SSH setup, the security hardening and audits page is the right entry point, and our post on why Linux powers top servers explains the foundation we build on. For identity and access management beyond the SSH layer, our work on central identity operations picks up where this post stops, and the piece on critical security updates covers what matters between hardening rounds.
Sources
- OpenSSH portable release notes, https://www.openssh.com/releasenotes.html (accessed 2026-11-04)
- OpenBSD, sshd_config(5), https://man7.org/linux/man-pages/man5/sshd_config.5.html (accessed 2026-11-04)
- Debian 12 release notes, https://www.debian.org/releases/bookworm/amd64/release-notes/ch-whats-new.en.html (accessed 2026-11-04)