Most intrusions start at port 22. I've watched logs fill with brute-force attempts within hours of spinning up a new server with default SSH config. The good news: hardening SSH takes maybe 30 minutes and cuts your attack surface by 80%.
This runbook covers what I do on every Linux box before it sees traffic. Not paranoid theater—just the changes that matter.
Disable Password Authentication
Password auth is your biggest liability. Bots will find your server and hammer it. Even a "strong" password loses to persistence.
The fix: Force key-based authentication only.
Edit /etc/ssh/sshd_config:
PasswordAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
Then reload:
sudo systemctl reload sshd
Gotcha: Make sure you've already added your public key to ~/.ssh/authorized_keys on the target server, or you'll lock yourself out. Test the new key before reloading on production.
Change the Default Port
Port 22 is scanned by every automated tool. Moving to a non-standard port (say, 2222) won't stop a targeted attacker, but it kills 99% of the noise.
In sshd_config:
Port 2222
Reload SSH and verify you can still connect:
ssh -p 2222 user@your-server
Then update your firewall rules to allow only 2222 inbound. If you're using ufw:
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp
Gotcha: Document this in your runbook or you'll forget it in six months when you hand off the server.
Disable Root Login
Don't let anyone SSH directly as root. Make them use a regular user, then sudo.
PermitRootLogin no
This forces privilege escalation to be logged separately and gives you an audit trail. Plus, if someone compromises a user account, they still need the sudo password (or your sudoers config) to go further.
Limit Login Attempts
SSH has built-in rate-limiting. Use it.
MaxAuthTries 3
MaxSessions 2
Three failed attempts and the connection closes. Two concurrent sessions per user prevents resource exhaustion from a single compromised account.
Use Protocol 2 Only
If you're running anything older than RHEL 6 or Ubuntu 12.04, you might have Protocol 1 enabled. It's broken. Remove it:
Protocol 2
Modern SSH defaults to 2, but check anyway on legacy systems.
Restrict SSH Access by IP (When Possible)
If your team works from a known office or VPN, lock SSH to those IPs:
ListenAddress 0.0.0.0
AllowUsers user1@203.0.113.0 user2@203.0.113.0/24
Or use a firewall rule:
sudo ufw allow from 203.0.113.0/24 to any port 2222
Gotcha: Don't do this if you have remote staff or on-call engineers connecting from home. You'll get the 3 a.m. page about being locked out.
Enable SSH Key Rotation Reminders
Keys don't expire by default. Set a calendar reminder to rotate them every 90 days. Yes, manually. A key compromised six months ago is still valid.
Generate a new key:
ssh-keygen -t ed25519 -C "user@hostname-2024"
Add the new public key to authorized_keys, test it, then remove the old one.
Monitor Failed Logins
Hardening SSH is half the battle. The other half is knowing when someone's trying to break in.
Check logs in real-time:
sudo tail -f /var/log/auth.log | grep sshd
For production, set up log aggregation. If you're using journalctl:
journalctl -u ssh -n 50 --no-pager
Set up a simple alert if you see repeated failed attempts from the same IP. Most monitoring tools (Prometheus, Datadog, Grafana Loki) can parse auth logs and trigger alerts.
Use SSH Config on Your Local Machine
Don't type ssh -p 2222 user@your-server every time. Create ~/.ssh/config:
Host prod
HostName your-server.com
User deployuser
Port 2222
IdentityFile ~/.ssh/id_ed25519_prod
ServerAliveInterval 60
ServerAliveCountMax 3
Now you just type ssh prod. The ServerAlive* settings keep the connection alive through idle firewalls.
Verify Your Changes
After editing sshd_config, always validate syntax:
sudo sshd -t
If it returns nothing, you're good. If there's an error, fix it before reloading.
Then test a fresh connection from another terminal before closing your current one:
ssh -p 2222 user@your-server
If that works, reload:
sudo systemctl reload sshd
Don't restart—reload keeps existing connections alive while applying new config.
The Checklist
Before you call SSH hardened, confirm:
- Password auth disabled
- Public key added to
authorized_keysand tested - Port changed from 22 to a non-standard number
- Root login disabled
- Max auth tries set to 3
- Max sessions set to 2
-
sshd -tpasses - Fresh connection test succeeds
- Firewall rules updated
- Changes documented in your runbook
- Log monitoring configured
What This Actually Stops
This hardens SSH against:
- Automated bot scans (port change alone kills most noise)
- Dictionary and brute-force attacks (key auth + rate limiting)
- Privilege escalation via direct root login (PermitRootLogin no)
- Resource exhaustion (MaxSessions)
What it doesn't stop:
- A targeted attacker with your private key (that's key management—different post)
- SSH zero-days (update your system regularly)
- Social engineering (that's a people problem — if you're unfamiliar with how these attacks work, the writeup on digitalwarga.id covers phishing tactics worth understanding)
Tomorrow's Action
Pull up your production server. Open sshd_config. Add the five core settings: disable password auth, change the port, disable root login, limit auth tries, and set max sessions. Validate, test, reload. Document it. Done.
If you're managing more than one server, automate this with Ansible or your config management tool. But do it once by hand first so you understand what breaks when.