Password auth off, root login off — that's roughly ninety percent of what SSH hardening actually buys you, and I already covered it in how I secure a fresh VPS. That post didn't have room for the parts that generate actual disagreement: which key type is worth generating, whether AllowUsers earns its line in sshd_config, why moving SSH off port 22 keeps showing up on every hardening checklist despite doing almost nothing, and what fail2ban is honestly still for once the thing it's supposedly protecting against — a guessable password — no longer exists on the box.
Why does the key type actually matter?
Because one of them is faster to verify, harder to get wrong, and smaller to carry around, and the other one is the thing everyone generates out of habit. Ed25519 is elliptic-curve, fixed at 256 bits, and gives you security roughly equivalent to a 3072-bit RSA key at a fraction of the size — the public key fits on one line you could read over the phone. It's also been OpenSSH's own recommended default since version 7.0 in 2015, and as of OpenSSH 9.5 in October 2023, ssh-keygen generates ed25519 by default if you don't specify a type at all. If you're still typing -t rsa out of muscle memory, you're actively opting out of the default your own tooling picked for you. There is a whole operational layer above keys, brute-force protection and what fail2ban still buys you that the XenGrowth practice documents.
RSA isn't broken. It's just the legacy accommodation now, not the default choice — you reach for it when something old enough to lack Curve25519 support is on the other end, and NIST's deprecation of 2048-bit RSA means that if you're generating an RSA key today anyway, 3072 bits is the floor, not 2048. ECDSA sits in an awkward middle spot: technically fine, but it depends on NIST curves that have drawn enough cryptographic side-eye over the years that most guidance just tells you to skip straight to ed25519 instead of arguing about it.
Key type | Effective strength | Where it's used | Generate it? |
|---|---|---|---|
ed25519 | ~256-bit curve, equivalent to RSA-3072 | ssh-keygen default since OpenSSH 9.5 | Yes — default choice for anything new |
RSA-3072/4096 | 3072-bit minimum for 2026 generation | Legacy servers without Curve25519 support | Only when the remote end forces it |
ECDSA | NIST P-256/384/521 curves | Rare, mostly historical | No — use ed25519 instead |
RSA-2048 | Below NIST's current floor | Old keys still in use | Rotate it out, don't generate new ones |
What does 'disable password auth' actually require?
Two lines, most guides say: PasswordAuthentication no and PermitRootLogin no. Both true, both necessary, and both incomplete on their own — which is the part that trips people up because the box looks hardened and isn't quite. sshd has a second authentication path, keyboard-interactive, configured by a separate directive (KbdInteractiveAuthentication, or ChallengeResponseAuthentication on older OpenSSH versions), and PAM modules invoked under that path can still prompt for a password even with PasswordAuthentication set to no. It's not a bug and it's not distro-specific mischief — it's just a second door that the first lock doesn't cover, because sshd was written to let PAM handle its own conversation with the client independently of the password directive.
PasswordAuthentication no — closes the obvious door.
KbdInteractiveAuthentication no (or ChallengeResponseAuthentication no on older OpenSSH) — closes the one PAM can reopen behind it.
PermitRootLogin no — not prohibit-password, which still lets root in with a key. If you genuinely need scripted root access, prohibit-password is the narrower option; if you don't, no means no path in at all, key or otherwise.
UsePAM yes can stay on — PAM still handles account and session management usefully; it's the auth path specifically that needs the second directive above, not PAM wholesale.
Restart sshd, then test from a second, still-open session before closing the first — the same rule from the original checklist applies every time you touch this file, because the failure mode isn't subtle. It's a locked door with no key on either side of it. The XenGrowth resource library covers the the operations side of this side of this.
Is AllowUsers worth the extra line?
AllowUsers deploy [email protected]/8 restricts SSH logins to an explicit list of username patterns, optionally scoped to a source host or network per entry. It's a second, independent gate: a leaked key for an account that isn't on the list still doesn't get in, and an account that shouldn't have interactive shell access at all (a service account, a deploy user meant only for git operations) never has to be a candidate for SSH login in the first place. AllowGroups does the same thing at the group level, which scales better once you're managing more than a handful of accounts by hand. Neither replaces the key-and-password work above — it's a name-based filter sitting on top of an already-locked door, and its main value is turning "which accounts can even attempt SSH" from an implicit fact about who happens to have a Unix account into something written down and enforced.
What other sshd_config directives are worth setting while you're in there?
A handful of directives get skipped because they're boring, not because they're wrong. None of them are the headline change PasswordAuthentication no is, but each closes a small, specific gap, and together they cost about four more lines in a file you already have open.
MaxAuthTries 3 — caps how many authentication attempts a single connection gets before sshd drops it, which limits how much a single TCP session can be used to hammer at key or PAM auth before it's forced to reconnect entirely
LoginGraceTime 30 — the default grace period for completing authentication is generous; cutting it to something like 30 seconds closes idle half-open connections faster instead of leaving them to linger
ClientAliveInterval 300 and ClientAliveCountMax 2 — together they drop a session that's stopped responding after roughly ten minutes, which matters less for security directly and more for not accumulating zombie sessions on a box you rarely reboot
AddressFamily inet — if the box has no real IPv6 use case yet, this stops sshd from listening on an IPv6 interface nobody's watching or including in firewall rules
Is moving SSH off port 22 security, or just tidiness?
Tidiness. I'll say it flatly because most posts on this topic won't: modern scanning tools enumerate all 65,535 ports on a single host in well under a minute, and a targeted scan finds your moved port exactly as fast as it finds port 22 — the search space isn't the bottleneck, connectivity is. What changes when you move SSH to some five-digit port isn't your exposure to a determined scan, it's the volume of automated, indiscriminate connection attempts that show up in auth.log from bots that only ever check the default port because checking it costs them nothing and checking everywhere costs marginally more. Fewer of those means a smaller, more legible log — which turns out to be the actual argument for doing it, not the obscurity argument everyone reaches for instead. For the AI agents and marketing automation angle, see XenGrowth on governed AI marketing workflows.
Moving the port doesn't shrink your attack surface. It shrinks your log file. Those aren't the same benefit, and only one of them is real.
What does fail2ban still buy once password auth is already off?
Less than the name implies, and I'd rather say that directly than let "brute-force protection" do work it can't actually do anymore. Brute-forcing a password requires a password to guess. Once PasswordAuthentication is no and the keyboard-interactive path is closed too, there's nothing left for a brute-force attempt to succeed against — every login attempt that isn't a valid key fails immediately, every time, regardless of how many times it's retried. Fail2ban banning an IP after five failed attempts in that world isn't preventing a compromise that was ever going to happen; it's preventing five thousand failed attempts from becoming five million, which is a resource and log-noise problem, not a security one.
That's still worth having. A box getting hammered by a botnet's SSH module burns CPU on cryptographic handshakes it never needed to attempt, and an auth.log that fills with the same rejected IP retrying every few seconds makes it much harder to notice the one login attempt that actually matters — an AllowUsers-matching account being tried from an unexpected network, say, or a key being presented that used to work and no longer should. Fail2ban's real job at that point is keeping the log small enough that an anomaly still looks like one instead of getting lost in repetition. That's a legitimate reason to run it. It's just a different reason than the one usually given.
Scenario | What fail2ban is doing | Is it "brute-force protection"? |
|---|---|---|
Password auth still enabled | Banning IPs before they exhaust the password space | Yes — this is the textbook case |
Password auth off, keys only | Damping connection floods, keeping auth.log legible | No — there's no password left to force |
A previously-valid key gets exposed elsewhere | Rate-limiting repeated presentation attempts, surfacing the anomaly in a smaller log | No — this is log hygiene catching a different failure |
Is there anything better than a raw key-pair once you have more than one server?
SSH certificates, if you get to the point where key management itself is the annoying part. Instead of every server trusting a static list of public keys that has to be updated everywhere when someone joins, leaves, or gets a new laptop, a CA signs short-lived certificates for each user, and every server is configured to trust the CA once instead of trusting individual keys forever. Revoking access becomes not issuing a new certificate rather than hunting down and removing a key from every authorized_keys file it was ever added to, and a stolen certificate expires on its own within hours instead of remaining valid until someone notices and manually revokes it. This is genuinely more infrastructure than a single-VPS setup needs — it's the right answer once you're managing access for a team across more than a handful of boxes, not before. XenGrowth on building one SEO and GEO content system approaches this from the AI search, GEO and discovery side.
When does a jump host beat hardening every box individually?
The moment you have more than one server. Hardening N boxes individually means N places where a key gets added, N places where it has to get revoked if a laptop is lost, and N separate audit trails to check if something looks wrong. A bastion — one small, deliberately minimal host with the only public-facing SSH listener, everything else behind it accepting SSH only from the bastion's internal address — turns that into one place. OpenSSH's native ProxyJump (ssh -J bastion target, or a corresponding config block) gets you there without manually forwarding an agent socket to an intermediate host, which matters: agent forwarding through a jump host you don't fully trust hands that host the ability to use your loaded keys for the duration of the session, where ProxyJump just tunnels the connection through without exposing the agent to it at all. If none of your boxes have a public IP to begin with — most self-hosted setups behind CGNAT or a residential connection — the bastion pattern and the port-22-exposure question both become moot in a different way, which is the actual subject of Cloudflare Tunnel vs. a reverse proxy.
Rank these by leverage and the list gets shorter than it looks. Key type barely matters if password auth is still on. Disabling password auth without also closing the keyboard-interactive path is half a fix that looks like a whole one. AllowUsers and a bastion are the layer that actually starts mattering once there's more than one account or more than one box. Fail2ban and a moved port are hygiene — worth doing, badly oversold, and neither one is what's standing between your server and a compromise. The thing standing between your server and a compromise is still the same line it was in the first post: PasswordAuthentication no, done correctly, on every path that leads to it.
Further reading from XenGrowth
The XenGrowth resource library — what you'll learn: how the commercial side of this work is run, across search, automation and revenue operations.
XenGrowth on governed AI marketing workflows — what you'll learn: how the teams running AI marketing agents keep them governed and measurable.
XenGrowth on building one SEO and GEO content system — what you'll learn: how search and AI-answer visibility get run as a single content system.
Where this work meets go-to-market
Working on keys, brute-force protection and what fail2ban still buys you inside a commercial team? XenGrowth's marketing operations practice publishes operator guides on the revenue side of this work.
Five questions on the parts of SSH hardening past PasswordAuthentication no. Answers and reasoning at the end.










