SSH hardening is one of those tasks that feels straightforward until you lock yourself out of your own server. The order of operations matters a lot here. Do the steps wrong and you're looking at a rescue console or a full server rebuild.
This guide does everything in the right order. Read it once before running any commands.
Before you start: open a second SSH session
This is not optional. Before making any changes to SSH configuration, open a second terminal and connect to your server. Keep it open. If your primary session disconnects, you have a working backup. If your backup also disconnects — you've locked yourself out and need the provider's console.
# Terminal 1: your working session where you make changes # Terminal 2: keep this connected as a backup the entire time # Verify you can connect before starting: ssh user@your-server-ip "echo connected"
Step 1: Set up SSH key authentication
Do this first, before disabling password authentication. If you disable passwords before setting up keys, you lock yourself out.
# On your LOCAL machine — generate a key if you don't have one: ssh-keygen -t ed25519 -C "your-server-name" # Copy your public key to the server: ssh-copy-id user@your-server-ip # Verify key auth works — open a NEW terminal and connect: ssh user@your-server-ip # If this connects without asking for a password, keys are working
Only proceed to the next step after confirming key authentication works in a fresh terminal.
Step 2: Disable password authentication
sudo nano /etc/ssh/sshd_config # Find and change these lines: PasswordAuthentication no PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys
Don't restart SSH yet. Make all your config changes first, then restart once.
Step 3: Disable root login
# In /etc/ssh/sshd_config: PermitRootLogin no
If you need to run commands as root, use sudo from your regular user account. Direct root SSH login is unnecessary and increases your attack surface.
Step 4: Limit login attempts
# In /etc/ssh/sshd_config: MaxAuthTries 3 MaxSessions 5 LoginGraceTime 30
MaxAuthTries 3 limits how many password attempts before the connection is dropped. LoginGraceTime 30 gives users 30 seconds to authenticate before the connection times out.
Step 5: Disable unused authentication methods
# In /etc/ssh/sshd_config: X11Forwarding no AllowAgentForwarding no AllowTcpForwarding no PrintMotd no
Unless you specifically need X11 forwarding or TCP tunneling, disable them. These features increase attack surface without benefit for most setups.
Step 6: Apply the config
# Test the config before restarting: sudo sshd -t # If no errors, restart SSH: sudo systemctl restart sshd # Verify SSH is running: sudo systemctl status sshd
Critical: After restarting SSH, test that your key authentication still works from your backup terminal before closing your primary session. If the backup terminal can connect — you're done. If it can't connect — something went wrong and you still have your primary session to fix it.
Step 7: Set up fail2ban
sudo apt install fail2ban -y sudo tee /etc/fail2ban/jail.local << 'EOF' [DEFAULT] bantime = 1h findtime = 10m maxretry = 3 backend = systemd [sshd] enabled = true port = ssh maxretry = 3 bantime = 24h EOF sudo systemctl enable --now fail2ban sudo fail2ban-client status sshd
Optional: change the SSH port
Changing from port 22 to a high-numbered port (e.g., 2222 or 22022) eliminates the vast majority of automated scanning. It's security through obscurity — not a substitute for proper hardening — but it drastically reduces log noise.
# In /etc/ssh/sshd_config: Port 2222 # Update UFW to allow the new port: sudo ufw allow 2222/tcp sudo ufw delete allow 22/tcp # Only after confirming the new port works # Restart SSH: sudo systemctl restart sshd # Connect on the new port to verify: ssh -p 2222 user@your-server-ip
Important: update your firewall rules before restarting SSH on the new port. If you close port 22 in UFW before opening the new port, you lock yourself out.
Verify your hardening
# Check current SSH config: sudo sshd -T | grep -E 'passwordauthentication|permitrootlogin|pubkeyauthentication|maxauthtries' # Check fail2ban is active: sudo fail2ban-client status sshd # Check who's been blocked: sudo fail2ban-client status sshd | grep "Banned IP" # Check for recent login attempts in the log: sudo journalctl -u sshd --since "1 hour ago" | grep -i "failed|invalid"
The complete sshd_config changes summary
# Changes to make in /etc/ssh/sshd_config: PasswordAuthentication no PubkeyAuthentication yes PermitRootLogin no MaxAuthTries 3 MaxSessions 5 LoginGraceTime 30 X11Forwarding no AllowAgentForwarding no AllowTcpForwarding no PrintMotd no
Audit your UFW firewall rules alongside SSH hardening — check for Docker bypass risk, missing default-deny, and open ports that shouldn't be public.
Open Firewall Auditor →