SSH Hardening Guide: Key-Only Auth, Bastions, and Lockdown
Practical SSH hardening: enforce key-only login, disable root, allowlist users, front access with a bastion, and rate-limit brute force.
An SSH server exposed to the internet is one of the most probed things you can run. Automated scanners find port 22 within minutes and start guessing usernames and passwords around the clock. A default SSH configuration will survive that, but survival is a low bar. Hardening SSH turns a machine from a target that merely holds up into one where the common attacks have no door to knock on. The steps are well established and mostly quick, and they build directly on how SSH works, covered in How SSH Works.
The strategy is deny-by-default at every layer: only keys, only named users, only through a controlled gateway, and slow down anything that still tries. Each layer is useful alone, and together they leave very little for an attacker to work with.
Start with key-only authentication
The most important change is also the simplest. Turn off password login entirely and accept only public-key authentication.
PasswordAuthentication no
PubkeyAuthentication yes
Passwords are the surface every brute-force scanner attacks. With password authentication off, guessing usernames and passwords stops working no matter how many attempts are made, because the server will not accept a password at all. Public-key authentication proves identity with a signature and never sends a secret across the wire, so there is nothing to guess and nothing to phish. This one change removes the entire category of credential-guessing attacks against your SSH service.
Before you disable passwords, make sure your public key is installed and you have tested a key-based login, so you do not lock yourself out.
Choose a modern key type
Not every key is equal. The algorithm behind the key decides how much cryptographic margin you are working with and how large the on-disk material is.
- Ed25519 is the current default recommendation. It uses elliptic-curve cryptography, produces small keys, verifies quickly, and has resisted the implementation pitfalls that dogged older schemes. Generate one with
ssh-keygen -t ed25519. - RSA is still acceptable at 3072 or 4096 bits, and remains the most compatible choice for old servers. Anything smaller than 2048 bits should be retired. RSA-1024 is broken for practical purposes and must never guard a live host.
- ECDSA works but carries more historical caution around the quality of its random number generation, so Ed25519 is the cleaner pick when both are available.
- DSA is obsolete. OpenSSH has removed it, and any DSA key you still find is a liability to rotate out immediately.
The private key is the secret that matters. Protect it with a passphrase so a stolen laptop or a leaked backup does not hand an attacker a working credential. A passphrase encrypts the private key at rest, and an SSH agent lets you unlock it once per session rather than typing it on every connection. That combination keeps convenience high without leaving the raw key sitting in the clear.
Handle agent forwarding with care
SSH agent forwarding lets a key on your laptop authenticate onward from a remote host without copying the private key there. It is convenient for hopping between machines, and it carries a real risk. Anyone with root on the intermediate host can talk to your forwarded agent socket and use your key while your session is live. Forward the agent only to hosts you trust, prefer ssh -J (native jump-host proxying) over blanket ForwardAgent yes, and never enable agent forwarding by default in your client config. On a bastion in particular, forwarding is the difference between a compromised jump host being an inconvenience and being a skeleton key to everything behind it.
Never allow root to log in directly
Direct root login over SSH is a longstanding bad default. Two reasons to disable it:
- Accountability. If everyone logs in as root, the logs cannot tell you who did what. Logging in as a named user and escalating with
sudoties every action to a person. - Attack surface. Root is the one username an attacker can always assume exists. Allowing root login hands them a valid target for free.
PermitRootLogin no
Administrators log in as themselves and elevate only when needed. This aligns with least privilege, covered in Least Privilege Explained, and it means a single compromised session is not automatically full control of the box.
There is a defense-in-depth angle here too. When root cannot log in directly, an attacker who steals a user's key still lands as an ordinary account and has to clear a second hurdle to reach root, which is another point where logging and alerting can catch them. Escalation through sudo records who ran what, when, and from where. That audit trail is worth as much as the barrier itself, because it turns a quiet compromise into something an investigation can reconstruct after the fact.
Allowlist who can connect
By default any account on the system with valid credentials can attempt SSH. Turn that into an explicit allowlist so only named users or groups are ever permitted.
AllowUsers alice bob
AllowGroups ssh-users
Now an attacker who somehow obtains credentials for a service account or an unexpected user still cannot authenticate, because that account is not on the list. This is deny-by-default applied to identity. Keep the list small and review it, since stale entries are how old access lingers.
| Control | sshd setting | What it removes |
|---|---|---|
| Key-only auth | PasswordAuthentication no | Brute-force password guessing |
| No root login | PermitRootLogin no | The one guaranteed username, and anonymous admin actions |
| User allowlist | AllowUsers / AllowGroups | Logins from any unexpected account |
| Disable unused auth | ChallengeResponseAuthentication no | Extra authentication paths you do not use |
Every SSH hardening change carries one real risk: locking yourself out. Before you restart the SSH service, validate the config with sshd -t, and keep a second logged-in session open while you apply changes. If the new config breaks your access, the existing session lets you roll it back. For cloud instances, keep a console or recovery path that does not depend on SSH. Careful sequencing here prevents the most common self-inflicted outage.
Harden the rest of the sshd config
Key-only auth, no root login, and a user allowlist cover the big wins. A handful of further sshd_config directives close smaller gaps and reduce the surface an authenticated or half-authenticated connection can reach.
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
MaxSessions 4
MaxStartups 10:30:60
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
PermitEmptyPasswords no
Each line earns its place:
- MaxAuthTries caps how many authentication attempts a single connection may make before the server drops it. A low value trims the work a scanner can extract from each socket.
- LoginGraceTime limits how long an unauthenticated connection may sit open. Short values starve slow-drip attacks that try to hold connection slots.
- ClientAliveInterval and ClientAliveCountMax together close idle sessions the server can no longer reach, so a forgotten terminal or a dropped network does not leave a live session hanging as an open door.
- MaxSessions and MaxStartups bound concurrency, which blunts resource-exhaustion attempts against the daemon.
- X11Forwarding, AllowAgentForwarding, AllowTcpForwarding, and PermitTunnel turn off tunneling features you probably do not use. Each one is a channel an attacker could otherwise repurpose to pivot deeper into the network. Leave them off and enable per-host only where a real workflow needs them.
- PermitEmptyPasswords no is a belt-and-braces guard so a misconfigured account with a blank password can never slip through.
Prune weak algorithms
OpenSSH negotiates ciphers, message authentication codes, and key-exchange methods with the client. Older builds keep some legacy algorithms enabled for compatibility. Restrict the daemon to modern primitives so a downgrade cannot land you on a weak cipher.
KexAlgorithms curve25519-sha256,[email protected]
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]
The exact list shifts as algorithms age, so treat this as a snapshot to review, not a permanent answer. The principle holds: offer only authenticated-encryption ciphers and strong key exchange, and drop anything with known weakness. Keeping the SSH package patched matters just as much, because protocol and implementation fixes arrive through updates.
Aggressively pruning ciphers and MACs can lock out older clients, embedded devices, or automation that speaks an outdated SSH dialect. Before you tighten the algorithm lists on a shared server, confirm what needs to connect. Test from a representative client, watch the auth log for negotiation failures, and roll the change out on one host before the fleet. The goal is a strong floor that still admits every legitimate client, so verify rather than assume.
Put a bastion in front
Rather than exposing SSH on every server, route all access through a single hardened gateway called a bastion or jump host. Administrators connect to the bastion, and from there to internal servers, which accept SSH only from the bastion.
The benefits compound:
- One door to watch. All SSH sessions pass through a single point you can log, monitor, and alert on.
- Smaller surface. Internal servers are not reachable from the internet at all, so scanners never see them.
- Simpler access changes. Granting and revoking access happens at one place.
This is a form of network segmentation applied to remote access. It maps to MITRE ATT&CK's Remote Services technique (T1021), a common path attackers use to move between machines once inside. Confining SSH to a bastion narrows that lateral movement. See Network Segmentation Guide for the wider pattern, and RDP Security Guide for the same thinking applied to Windows remote access.
Slow down and detect what still tries
Even with key-only auth, scanners will keep hammering port 22 with connection attempts. Rate-limiting tools such as fail2ban watch the logs and temporarily block source addresses that fail too many times. This is worth doing, with one caveat.
Rate limiting is a noise and detection control, not a primary defense. With passwords disabled, those attempts were already failing. fail2ban reduces log noise, trims resource use, and can flag a source worth investigating, but the real protection came from key-only authentication. Treat it as a complement, never as the thing standing between you and a break-in.
What about changing the port
A frequent suggestion is to move SSH off port 22 to something obscure. It is worth being precise about what this does. Changing the port cuts down the volume of automated scanning that finds the service, which quiets your logs and saves a little noise. It does nothing against an attacker who bothers to scan the full port range, and every real adversary does. So port changes are cosmetic hardening. They can sit on top of the controls above, but they must never take the place of key-only auth or the user allowlist. Judged as a security boundary, an obscure port is close to worthless; judged as log-noise reduction, it is a minor convenience. Keep that ordering straight and you will not mistake obscurity for protection.
Two settings that genuinely reduce exposure are worth pairing with any port choice. An idle-session timeout closes forgotten sessions that would otherwise linger as an open door, and a short login grace period limits how long an unauthenticated connection can sit consuming a slot. Both shrink the window an attacker has to work with, and neither costs anything to enable.
It is tempting to pick one control and call SSH hardened. The strength comes from stacking them. Key-only auth removes guessing, the user allowlist removes unexpected accounts, no-root-login removes the guaranteed target, the bastion removes direct exposure, and rate limiting cuts the noise. An attacker has to beat all of them at once, and each layer covers a different failure of the others.
Scale trust with SSH certificates
Copying public keys onto every server works for a handful of machines. Across a fleet it becomes a mess of authorized_keys files that drift out of sync, keep access for people who left, and give you no clean way to revoke a single credential in a hurry. SSH certificates solve this by introducing a certificate authority.
The pattern works like this. You stand up a trusted CA key. Each server is configured to trust that CA with a single TrustedUserCAKeys line, and stops caring about individual authorized_keys entries. When a user needs access, the CA signs their public key into a certificate that carries an expiry and a list of principals (the identities or roles it is valid for). The user presents the certificate, the server checks the CA signature and the expiry, and access is granted only if both hold.
Two properties make this stronger than scattered static keys:
- Short lifetimes. A certificate can be valid for hours instead of forever. A leaked credential expires on its own, which shrinks the window an attacker has and removes the standing risk of keys that outlive their owner.
- Central revocation. You revoke at the CA or through a revocation list rather than editing files on every host. Offboarding becomes one action instead of a sweep across the fleet.
Host certificates are the mirror image and just as useful. Signing each server's host key with a host CA lets clients verify they reached the real machine, which removes the blind "yes" that users otherwise type to the unknown-host prompt. That prompt is a genuine phishing and interception risk, and host certificates retire it.
Certificates add moving parts: the CA key becomes a high-value secret you must protect, and you need a process to issue and renew. For a fleet of any size the trade is worth it, because static-key sprawl is one of the quiet ways old access lingers long after it should.
Detection signals
Hardening reduces the ways in. Monitoring tells you when someone is testing them. SSH activity lands in predictable places, and a few concrete signals separate normal traffic from something worth an alert.
- Repeated auth failures from one source. Bursts of
Failed passwordorInvalid userlines for accounts that do not exist are the signature of automated scanning. With passwords off they cannot succeed, but a sudden spike, or attempts against a real username, is worth a look. - Accepted logins from unexpected geography or hours. A successful
Accepted publickeyfor an admin at 3am from a country you do not operate in deserves scrutiny, even when the key is valid, because a stolen key looks exactly like a real one until the context gives it away. - New or changed
authorized_keysfiles. An attacker who gains a foothold often plants a key to keep access. File-integrity monitoring on~/.ssh/authorized_keysand onsshd_configturns that persistence move into an alert. - Logins that skip the bastion. If policy says every session flows through the jump host, a direct connection to an internal server that bypasses it is a strong signal of either a misconfiguration or an intruder who found another path.
- sshd config or service restarts you did not schedule. An attacker loosening the daemon (re-enabling passwords, adding a permitted user) leaves a restart and a config change in the record. Watch for both.
- Unusual outbound tunnels or port forwards. Forwarding activity from a host that has no business tunneling can indicate an attacker using SSH for exfiltration or pivoting, which maps to the Remote Services technique (T1021) in MITRE ATT&CK.
Ship these logs off the host to a central collector. An attacker with local access can edit or delete local logs, so a copy they cannot reach is what preserves the evidence.
SSH authentication methods compared
The choice of authentication method drives most of the risk. This is how the common options line up.
| Method | Brute-forceable | Phishable | Scales to a fleet | Notes |
|---|---|---|---|---|
| Password | Yes | Yes | Poorly | The surface every scanner attacks. Disable it. |
| Public key | No | Hard | With effort | Strong, but static keys sprawl and rarely get rotated. |
| Key plus passphrase | No | Hard | With effort | Adds protection if the private key file is stolen. |
| SSH certificate | No | Hard | Well | Short-lived, centrally revocable, best for scale. |
| Password plus MFA/OTP | Slowed | Reduced | Moderately | Better than a bare password, weaker and noisier than keys. |
Key-based methods remove guessing outright. Certificates add lifetime limits and central control on top. Password with a second factor is a real improvement where keys are not practical, and it is still the weaker family, because the password remains a phishable secret that travels.
Common misconceptions
A few beliefs about SSH hardening are common enough to correct directly.
- "A non-standard port hides the server." It reduces automated scan volume and nothing more. A targeted attacker scans all ports. Treat a port change as log-noise reduction, never as a security control.
- "fail2ban is my main defense against brute force." With passwords disabled the brute force was already failing. Rate limiting trims noise and flags sources to review. Key-only auth is what actually stops guessing.
- "Key-based auth means I never rotate anything." Keys leak through backups, stolen laptops, and copied files. Without rotation and revocation a key is a standing risk that outlives the person who made it. Certificates or a rotation process fix this.
- "Disabling root login is enough on its own." It removes the guaranteed username and forces accountable escalation. It does nothing about weak passwords on other accounts or unpatched software. It is one layer among several.
- "Internal servers do not need hardening." Lateral movement is a normal stage of an intrusion. A flat internal network lets one compromised host reach many. Harden internal SSH and segment it the same way you would an exposed host.
- "MFA on SSH replaces key-only auth." A second factor on top of a password helps, and it is still built on a phishable secret. Public keys remove the guessable credential entirely, which is the stronger foundation.
How SSH hardening evolved
SSH replaced Telnet and the r-commands (rlogin, rsh) precisely because those protocols sent credentials and sessions in the clear, where anyone on the path could read them. Encrypting the channel was the first leap. The defaults that shipped in the early years reflected a friendlier internet: root could log in directly, passwords were the norm, and forwarding features were on to be helpful.
The threat model changed as automated mass scanning became constant background noise on every public address. Guidance shifted toward deny-by-default: keys over passwords, no direct root, explicit allowlists. Cryptographic practice moved on too, retiring DSA and small RSA keys in favor of Ed25519 and authenticated-encryption ciphers, and OpenSSH removed weak algorithms outright in later releases. The most recent chapter is managing trust at scale, where certificate authorities and short-lived credentials replace the sprawl of static keys copied across hundreds of hosts. The direction has been steady: fewer standing secrets, tighter defaults, and every session tied to a named person.
Frequently asked questions
Will disabling password authentication lock me out?
It will if you have not installed and tested a public key first. Confirm a key-based login works, keep a second session open, and validate the config with sshd -t before you reload. On cloud instances, keep a console or recovery path that does not depend on SSH.
Is changing the SSH port worth doing? Only as log-noise reduction. It lowers the volume of automated scanning that finds the service, and it stops nothing a targeted attacker will do. Never let a port change substitute for key-only auth or the user allowlist.
Do I still need fail2ban if passwords are disabled? It is optional and helpful. With passwords off, the brute-force attempts already fail, so fail2ban is mostly trimming log noise, cutting resource use, and flagging sources worth a look. Treat it as a complement to key-only auth, never the main defense.
Ed25519 or RSA for my keys? Prefer Ed25519 for new keys: small, fast, and free of the older implementation pitfalls. Use RSA at 3072 or 4096 bits where you need compatibility with older servers. Retire anything smaller than 2048 bits, and retire DSA entirely.
When should I move from plain keys to SSH certificates?
When key management starts to hurt: many hosts, changing staff, and authorized_keys files drifting out of sync. Certificates give short lifetimes and central revocation, which turn offboarding and rotation into a single action instead of a sweep across every server.
Should a bastion host allow agent forwarding?
Be very careful. Anyone with root on the bastion can use a forwarded agent while your session is live, which makes the jump host a potential skeleton key. Prefer native jump-host proxying with ssh -J, and do not enable agent forwarding by default.
How do I know if my SSH server is under attack?
Watch the auth logs for spikes in failed attempts and invalid-user lines, accepted logins from unexpected places or hours, changes to authorized_keys or sshd_config, and any session that bypasses the bastion. Ship the logs to a central collector so a local intruder cannot erase the evidence.
A practical checklist
Bring it together into an order you can apply safely:
- Install and test your public key, then set
PasswordAuthentication noandPubkeyAuthentication yes. - Set
PermitRootLogin noand confirm you can escalate withsudoas your normal user. - Add
AllowUsersorAllowGroupsto allowlist exactly who may connect, and review it regularly. - Disable authentication methods you do not use and keep the SSH software patched and on modern algorithms.
- Front access with a bastion so internal hosts are never directly exposed, and log every session through it.
- Add rate limiting with fail2ban or equivalent to cut brute-force noise, as a complement to the above.
- Scale trust with SSH certificates for larger fleets, so you issue short-lived, revocable credentials instead of scattering static keys across every host.
Each step is small. Applied together they take SSH from a service that endures constant attack to one where the attacks have nothing to grip. The order matters mainly so you do not lock yourself out, which is why the config test and a spare session belong in every change.
Hardened SSH is one of the highest-return investments in a server's security. It closes the door that automated attackers rely on and gives you a clean, auditable path for the access you actually need. Build these controls in from the first server and they cost almost nothing to keep. For how exposed remote-access services turn into real intrusions, and the file-based lures that often follow, see our exploit intelligence dashboard.
Related guides
Sources & further reading
Related guides
- How SSH Works: Key Exchange, Host Keys, and Public-Key Auth
A plain guide to SSH: how the encrypted channel is set up, what host keys prove, and why public-key authentication beats passwords.
- Active Directory Security: Structure, Tiering & Hardening
How Active Directory is built, the tiered administration model, the weaknesses attackers exploit, and a hardening checklist for defenders.
- Common Ports Attackers Target, and How to Harden Them
A defensive reference to commonly targeted TCP/UDP ports: service, protocol, why attackers care, and one hardening note per port from FTP to MongoDB.