SSH brute-force attacks happen every day on every server with a public IP. If you run grep 'Failed password' /var/log/auth.log | wc -l and see a number in the thousands — congratulations, your server is being scanned constantly.
It happened to me in my first year as a sysadmin: an Ubuntu server on the default port 22, password auth enabled, nothing else. It took a whole afternoon to debug — root had been compromised at some unknown point, and an attacker had been quietly running a cryptominer since 3 AM. The logs showed over 40,000 failed attempts in 48 hours. That lesson cost more than any security course ever could.
This isn’t theory. I’m going straight to the options: what actually works, what wastes your time, and what order to do things in.
Comparing SSH Security Approaches
I’ve tried them all. There are 4 common approaches — and not all of them are worth investing in the way conventional wisdom suggests:
1. Change the SSH Port (Security by Obscurity)
Pros: Immediately reduces log spam. Script kiddies scanning port 22 by default will skip right past you.
Cons: Doesn’t actually make you more secure. Scanners like Shodan or Masscan sweep all 65,535 ports in minutes. This is noise reduction, not real security.
Verdict: Do it if you can, but don’t stop there.
2. Disable Password Auth, Use SSH Keys
Pros: Completely eliminates password brute-force. This is the highest-impact change at the lowest cost.
Cons: Requires careful key management. Losing your private key without a backup means losing access entirely.
Verdict: Non-negotiable. Priority number one, no debate.
3. Fail2ban / Rate Limiting
Pros: Automatically bans IPs after 3–5 failed attempts within 10 minutes. Effective against simple scanning scripts.
Cons: Distributed botnets hitting from thousands of different IPs will bypass Fail2ban entirely. Adds a dependency you need to maintain.
Reality: Worth having, but as a second layer — after you’ve already disabled password auth.
4. Firewall IP Whitelisting
Pros: Extremely effective if you always connect from a few fixed IPs. Nothing gets through a firewall that blocks at the source.
Cons: Dynamic IPs — mobile, cafes, hotels — become a problem immediately. You need a VPN or jump host to do this properly. When you do connect, running tmux inside your SSH session protects you if the connection drops unexpectedly.
Verdict: Ideal for production servers. Hard to apply if you work from many different locations.
Priority Order: What to Do First?
Here’s the order I follow, from highest to lowest impact:
- Disable root login + disable password auth + enable SSH keys
- Restrict which users are allowed to SSH
- Change the port (optional, mainly to reduce log noise)
- Install Fail2ban
- Firewall IP restriction (if your workflow allows it)
Practical Implementation
Step 1: Generate an SSH Key Pair and Upload It to the Server
Run this on your local machine — not on the server:
# Generate a key pair using ed25519 (stronger than RSA 2048)
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519
# Copy the public key to the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip
# Test the connection before disabling password auth!
ssh -i ~/.ssh/id_ed25519 user@your-server-ip
Important: Test key-based login BEFORE disabling password auth. Skip this step and you’ll lock yourself out. Also verify the user account you’re logging in as has the correct permissions — see our guide on managing users and permissions on Linux if you need a refresher.
Step 2: Harden the SSH Configuration File
sudo nano /etc/ssh/sshd_config
Lines to change or add:
# Disable root login
PermitRootLogin no
# Disable password authentication
PasswordAuthentication no
# On OpenSSH 9.0+, ChallengeResponseAuthentication was renamed to KbdInteractiveAuthentication
ChallengeResponseAuthentication no
UsePAM no
# Allow only SSH key authentication
PubkeyAuthentication yes
# Restrict SSH access to specific users (replace 'youruser' with your actual username)
AllowUsers youruser
# Change the port (optional)
# Port 2222
# Reduce timeout and retry limits
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
# Disable rarely used, higher-risk features
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
# Note: the 'Protocol 2' directive was removed in OpenSSH 7.6+ — no need to add it
Validate the syntax before reloading — follow this order exactly. If you’re unfamiliar with managing services with systemd, this reload pattern applies to any daemon config change:
# Check config for errors before applying
sudo sshd -t
# Only reload if there are no errors
sudo systemctl reload sshd
Step 3: Install and Configure Fail2ban
sudo apt install fail2ban -y # Ubuntu/Debian
# or
sudo dnf install fail2ban -y # RHEL/AlmaLinux
Create a separate local config file — don’t edit the original, it will be overwritten on package updates:
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
# If you changed the port: port = 2222
logpath = %(sshd_log)s
maxretry = 3
bantime = 3600
findtime = 600
ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Check status
sudo fail2ban-client status sshd
Step 4: Basic Firewall with UFW
# Allow SSH (if you changed the port, replace 22 with the new port)
sudo ufw allow 22/tcp
# Or restrict to a specific IP only
sudo ufw allow from 1.2.3.4 to any port 22
sudo ufw enable
sudo ufw status verbose
Verification Checklist After Setup
Don’t relax just yet — run through this checklist to make sure everything is working correctly:
# View recent successful logins
last -n 20
# View recent failed attempts
grep 'Failed password\|Invalid user' /var/log/auth.log | tail -20
# Confirm Fail2ban is running
sudo fail2ban-client status
# Check which port SSH is listening on
ss -tlnp | grep ssh
A command I use regularly for a quick SSH config audit:
sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|pubkeyauthentication|allowusers'
Expected output when properly hardened:
permitrootlogin no
passwordauthentication no
pubkeyauthentication yes
allowusers youruser
Common Mistakes When Hardening SSH
- Disabling password auth before uploading your key: You’ll lock yourself out. Always test key auth first.
- Forgetting to add your IP to Fail2ban’s
ignoreip: You’ll ban yourself after a few typos. - Editing
/etc/ssh/sshd_configwithout reloading: The config hasn’t been applied — you think you’re done but nothing has changed. - Using RSA 1024: It’s been weak for a long time. Use ed25519 or at minimum RSA 4096.
Summary
Three things are mandatory — no debate — if your server has SSH exposed to the internet: disable root login, disable password auth, enable SSH keys. Those three steps account for 90% of your security gains. Everything else is just hardening on top — including setting up SSL/TLS with Certbot for any web-facing services running on the same machine.
Fail2ban and firewall whitelisting are useful, but don’t rely on them as your primary defense. Attackers have the patience and the botnets to work around rate limits. But nobody can brute-force an ed25519 private key — it would take billions of years of computation to exhaust the 256-bit key space. For physical security threats — a stolen or seized server — pair this with Linux disk encryption with LUKS.

