SSH Hardening
Several topics covered separately earlier in this module — key authentication (SSH Key Authentication), restricting root login (SSH Configuration), and verifying host identity (SSH-Agent and Host Verification) — are each one piece of securing a production server. This article pulls them together into a single, coherent hardening approach and adds a few settings that only make sense in that combined context.
Why the Default Configuration Isn't Enough
Port 22 on any internet-facing IP address gets scanned constantly by automated bots, which try common usernames (root, admin, test) with password guesses. This isn't a targeted attack — it's continuous background noise, and any newly provisioned server feels this scanning traffic within minutes. A default OpenSSH configuration — password login enabled, root login allowed — is a fairly soft target in that environment.
Hardening isn't a single "magic" setting; it's layering several independent protections on top of each other — defense in depth: each one simple on its own, but together reducing the attack surface substantially.
The Core Checklist
1. Move to Key Authentication Only
As covered in SSH Key Authentication, every user who needs access gets a key set up first, and only then is password login disabled entirely:
KbdInteractiveAuthentication no closes a mechanism that, on some systems, can still leave a password-style prompt open through a different code path even after PasswordAuthentication is disabled.
2. Disable Direct root Login Entirely
Administrative tasks are done through a regular user plus sudo — a practical application of the least-privilege principle from Root and sudo, and it also enables auditing: every administrative action is logged under a specific username in the sudo log, rather than under a shared root identity.
3. Restrict Login to Specific Users
or at the group level:
This adds a layer of protection for service accounts that exist on the system but have no legitimate need for SSH access (www-data, postgres, and similar): they can't log in even if they somehow had a matching key, simply because they're not on the list.
4. Limit Authentication Attempts
MaxAuthTries— how many failed authentication attempts are allowed within a single connection;LoginGraceTime— how long (in seconds) a connection has to complete authentication; exceeding it drops the connection automatically, preventing idle, half-open connections from tying up resources.
5. Automatically Drop Idle Sessions
The server asks the client for a response every 300 seconds; if two consecutive checks get no response, the session is dropped. This prevents a forgotten, still-open terminal session from lingering indefinitely.
6. Changing the Port — What It Does and Doesn't Do
Note
Changing the port is not a security measure by itself, only a way to reduce noise. It doesn't strengthen the SSH protocol itself and won't stop a targeted attacker — a port scan finds an open port in seconds regardless. What it does cut out is the bulk of automated, mass bot scanning, since most of it only checks the standard port 22. Changing the port is useful, but only as an addition on top of the measures above, never as the only protection.
Changing the port means the firewall rule needs updating too — a direct continuation of the logic covered in Firewall and ufw:
Danger
Before changing the port, add the firewall rule for the new port first, and test the new port in a new terminal while keeping the old session open. Otherwise, once the change takes effect, you risk being unable to reach the server through either the old or the new port.
Auditing the Cryptographic Configuration
The checklist above hardens who may log in and how many times they may try; a separate dimension is which cryptographic algorithms the server will negotiate. Modern OpenSSH already disables the genuinely broken ones (the old ssh-rsa/SHA-1 signature, CBC-mode ciphers, weak DH groups) by default, so on a current Ubuntu/RHEL release this is usually an audit task, not a rewrite — and pasting a random Ciphers/KexAlgorithms list from an old blog post often weakens a server by pinning it to outdated choices.
The practical approach is to measure first, with the ssh-audit tool:
It flags any weak key-exchange, cipher, MAC, or host-key algorithm the server still offers and recommends what to remove. Only if it reports something weak do you then restrict it explicitly — in a drop-in, consistent with SSH Configuration:
# /etc/ssh/sshd_config.d/99-crypto.conf
KexAlgorithms curve25519-sha256,[email protected]
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]
Re-run ssh-audit afterward to confirm the weak algorithms are gone and nothing legitimate was cut off, then sudo sshd -t and reload as usual.
Fail2ban: Automatically Blocking Repeated Failed Attempts
MaxAuthTries limits attempts within a single connection, but it doesn't stop the same IP address from simply opening a new connection and trying again. That job belongs to Fail2ban: it watches SSH logs, and if a given IP address produces more than a configured number of failed attempts within a time window, it temporarily blocks that address at the firewall level. Installing and configuring Fail2ban is covered separately in Fail2ban — what matters for SSH hardening is that, combined with key-based authentication above, it also automatically isolates addresses that keep probing with keys that simply aren't authorized.
Interview angle
A related mechanism worth knowing about: PAM (Pluggable Authentication Modules) sits underneath sshd for account and session handling, and modules like pam_faillock can lock out a local account after repeated failed attempts, independent of Fail2ban's IP-based blocking. The two solve related but distinct problems — pam_faillock protects a specific account regardless of source IP, while Fail2ban protects the whole service from a specific source IP regardless of which account is targeted. A production server benefits from both, not one instead of the other. Configuring PAM in depth is its own topic and outside the scope of this article.
Two-Factor Authentication (2FA): When It's Needed
Key authentication already provides strong protection based on "something you have" — the private key file. In particularly high-risk environments, though — servers holding financial data, or ones bound by strict compliance requirements — combining that with a "something you know" or biometric factor, such as a TOTP code through libpam-google-authenticator, adds another layer. This isn't a requirement for an ordinary internal lab or a small project, but it can be a standard requirement in a larger production environment; the actual need depends on the organization's security policy and the sensitivity of what's being protected.
Rolling Out Hardening in Order
Changes should be applied and confirmed one at a time, not all at once:
- set up and confirm key authentication for every user who needs it;
- restrict access scope with
AllowUsers/AllowGroups, confirm; - enable
PermitRootLogin no, confirm; - enable
PasswordAuthentication no, confirm; - add further limits like
MaxAuthTries,LoginGraceTime,ClientAliveInterval; - (optional) change the port, update the firewall rule, confirm;
- install and configure Fail2ban.
After each step: check syntax with sudo sshd -t, reload with sudo systemctl reload ssh, and test in a new terminal without closing the current session — the safe-change procedure covered in detail in SSH Configuration.
Practical Scenario: Preparing a New Server for Production
Problem: a freshly provisioned Ubuntu Server VM still has the default OpenSSH configuration, with password-based root login enabled; it needs to be hardened before going live.
Step 1 — document the current state:
Step 2 — set up an administrative user and their key:
Following SSH Key Authentication, ssh-copy-id deploy@server is run next, and login is confirmed in a new terminal.
Step 3 — apply the checklist settings step by step:
sudo sed -i \
-e 's/^#*PermitRootLogin.*/PermitRootLogin no/' \
-e 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' \
-e 's/^#*MaxAuthTries.*/MaxAuthTries 3/' \
/etc/ssh/sshd_config
echo "AllowUsers deploy" | sudo tee -a /etc/ssh/sshd_config
sudo sshd -t
Step 4 — reload, and test in a new terminal while the old session stays open:
root@server should return Permission denied, while deploy should log in successfully and be able to use sudo.
Step 5 — install Fail2ban (covered in detail in Fail2ban):
Step 6 — final confirmation and documentation:
sudo sshd -T | grep -iE 'permitrootlogin|passwordauthentication|maxauthtries|allowusers'
sudo fail2ban-client status sshd
Record which settings were applied, when, and by whom — useful for a future audit or incident investigation.
Common Mistakes
Applying Every Change at Once, Without Testing
Enabling PasswordAuthentication no and PermitRootLogin no in the same pass, before key authentication has actually been verified, sharply raises the risk of being locked out of the server entirely.
Treating a Changed Port as the Only Defense
As noted above, this only reduces noise; it doesn't increase actual authentication strength.
Using Fail2ban as the Sole Defense, Without Key Authentication
Fail2ban slows down password-based brute-forcing, but if passwords are still enabled, enough time and a spread of IP addresses still leave real risk. Fail2ban is an additional layer, not a substitute for key authentication.
Not Verifying a Fallback Access Path After Hardening
If every SSH login path stops working for some reason (a misconfigured firewall rule, for instance), it's worth knowing in advance whether an alternative access path exists, such as a hosting provider's console or rescue mode.
Exercises
- Apply the checklist above on a lab server step by step, verifying in a new terminal after each step.
- Compare
sudo sshd -Toutput before and after hardening, and write down which values changed, as a table. - Set
MaxAuthTries 2for testing, then deliberately attempt to connect with a wrong key three times and observe how the connection is cut off. - Following Fail2ban, install it, confirm status with
fail2ban-client status sshd, then deliberately fail a password login several times (if still enabled) and observe your IP address being temporarily blocked.
Verification criterion: for exercise 1, the final state must show PermitRootLogin no and PasswordAuthentication no while a designated non-root user can still log in with a key and use sudo — if any part of that combination is missing, the rollout wasn't completed correctly.
Conclusion
SSH hardening isn't one setting — it's key authentication, restricted login scope, disabling root, limiting attempts, and tools like Fail2ban layered together. Each layer is simple on its own, but combined they defend a server against most automated, mass attacks and against a considerable share of targeted attempts as well. This closes out the SSH and remote management module — the next module moves into the Linux storage stack, starting with Disks and Block Devices.