SSH is the primary way to administer Linux servers and the main target of automated attacks. Proper hardening reduces the risk of a break-in by 95%.
Basic /etc/ssh/sshd_config configuration
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
AllowUsers deploy admin
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group14-sha256
Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
sshd -t && systemctl reload sshd
Generating and configuring SSH keys
ssh-keygen -t ed25519 -C "server-key-2026" -f ~/.ssh/myserver_ed25519
ssh-copy-id -i ~/.ssh/myserver_ed25519.pub -p 22 user@server
Two-factor authentication (TOTP)
apt install libpam-google-authenticator -y
google-authenticator
Add to /etc/pam.d/sshd: auth required pam_google_authenticator.so
In sshd_config: ChallengeResponseAuthentication yes and AuthenticationMethods publickey,keyboard-interactive
SSH hardening checklist
| Setting | Value | Priority |
|---|---|---|
| Non-standard port | 2222 or another | Medium |
| PermitRootLogin no | Mandatory | High |
| PasswordAuthentication no | After setting up keys | High |
| AllowUsers | Only needed users | High |
| MaxAuthTries 3 | Limit attempts | Medium |
| fail2ban | Blocking attacker IPs | High |
| 2FA (TOTP) | Additional factor | Medium |
Monitoring login attempts
journalctl -u ssh --since "1 hour ago" | grep "Failed"
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head -20
fail2ban-client status sshd
Common Hardening Errors
| Error | Cause | Fix |
|---|---|---|
| sshd: no hostkeys available -- exiting | A syntax error in the config, or the host key files are missing after a reinstall | Check the config: sshd -t, and regenerate keys if needed: ssh-keygen -A |
| Permission denied (publickey) | After disabling PasswordAuthentication, the public key wasn't copied or ~/.ssh has wrong permissions | Fix the permissions: chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys |
| Address already in use | The chosen port is already taken by another process or a stale sshd instance | Find the process: ss -tlnp | grep 2222 and stop the conflicting service |
| Connection refused on the new port | The firewall (ufw/iptables) doesn't allow the new SSH port yet | Allow the port: ufw allow 2222/tcp before restarting sshd |
| WARNING: UNPROTECTED PRIVATE KEY FILE! on the client | The private key file's permissions on the local machine are too open | Fix the permissions: chmod 600 ~/.ssh/myserver_ed25519 |
Comparing SSH Protection Methods
| Method | Protects against | Implementation effort |
|---|---|---|
| Changing the port | Automated "default port" scanning | Low, but won't stop a targeted attack |
| fail2ban | Brute-forcing based on failed-attempt logs | Low, set up in 5 minutes |
| 2FA (TOTP) | A stolen or leaked private key | Medium, requires a phone for every admin |
| Port knocking / SPA | Port reconnaissance by scanners (the port isn't visible at all) | High, makes connection troubleshooting harder |
When Hardening Won't Save You
- A 0-day vulnerability in OpenSSH itself — no configuration closes a bug in the daemon's code
- A compromised admin workstation with the private key stored on disk — the attacker gets in regardless of server settings
- An insider with legitimate access and their own key — hardening stops outside attacks, not abuse of granted rights
- A weak sudo password combined with physical or console access to the server (for example through a provider's panel) — SSH is not the only entry point
For brute-force protection, add Fail2ban: Protecting SSH and Nginx from Password Guessing, and a stronger second factor is covered in "Two-Factor Authentication for SSH and Web Panels".
Verifying the Result
After configuring, confirm the settings actually took effect:
# Show the active (applied, not just written) configuration
sshd -T | grep -Ei "permitrootlogin|passwordauthentication|port|maxauthtries"
# Confirm password login is actually rejected
ssh -o PubkeyAuthentication=no -o PasswordAuthentication=yes user@server
# Expected result: "Permission denied (publickey)"
# Check that the old port 22 is closed from the outside
nmap -p 22,2222 server_ip
If SSH needs to be hidden from scanners entirely, the next step is putting it behind a WireGuard VPN on a VPS and opening the port only inside the tunnel.
Frequently asked questions
What should you do if the new configuration is saved but the SSH connection stops working entirely?
If the new configuration blocks connections, first check the syntax with sshd -t and confirm the old port is still open in another terminal tab, since that is why the old session should stay open until the new one works. If access is lost entirely, the provider's console or hypervisor panel is the way to restore the previous values.
Is it safe to reuse the same private key for access to several servers?
Reusing one private key across several servers works technically, but it breaks the principle of isolation: a leak of that single key from the administrator's workstation instantly opens every server at once instead of just one. It is both simpler and safer to generate a separate key pair for each server, or at least for each distinct access role.
Should password login be disabled right away, before two-factor authentication is actually set up?
Password login should only be turned off once key-based login has actually been tested and confirmed working, otherwise any mistake in the key setup risks locking the administrator out before that point. Two-factor authentication makes more sense as an extra layer added on top of a working key-based login, not as a replacement for a temporary password.
How do you regain access to a server if AllowUsers is set incorrectly and login is locked out for everyone?
If AllowUsers has a typo or omits the needed account, SSH login closes for everyone, including the administrator, and the file can no longer be fixed through that same SSH connection. Recovery goes through the hosting provider's console or the hypervisor panel — local access that bypasses the network SSH path, letting you edit the configuration and restart the service.