
Last week, the IronWorm malware hit 36 npm packages and self-propagated through dependency chains. The week before that, I showed how Git bisect can track down broken commits — the same kind of methodical debugging that catches these attacks early. That OpenAI rolled out Lockdown Mode because AI agents were getting tricked into executing arbitrary code. The theme is clear: attackers are everywhere, and the services we run are only as secure as the configurations we leave in place.
SSH is one of those services. It’s the front door to every Linux server, cloud instance, and Raspberry Pi you own. And out of the box, that front door is wide open.
I’ve managed government servers for over a decade, and the number of times I’ve logged into a fresh VPS only to find the default SSH config still in place is alarming. Password authentication enabled. Root login allowed. Port 22 sitting there like a welcome mat for bots.
Here’s the good news: hardening SSH takes about 15 minutes. These seven changes will block the vast majority of automated attacks and make your server dramatically harder to compromise.
Prerequisites
You’ll need:
- Root or sudo access to a Linux server (Ubuntu, Debian, CentOS — the commands work across all of them)
- A local machine with
sshandssh-keygeninstalled (macOS, Linux, or Windows with WSL/OpenSSH) - A text editor on the server (
nanoorvim)
Important: Keep your current SSH session open throughout this process. Every change should be tested in a new terminal window before you close the old one. Locking yourself out of your own server is a rite of passage — let’s skip it.
Step 1: Generate a Strong SSH Key Pair
Passwords are the weakest link in SSH security. They’re guessable, crackable, and phishable. SSH keys replace them entirely with cryptographic proof of identity.
On your local machine (not the server), generate an Ed25519 key pair:
ssh-keygen -t ed25519 -C "[email protected]"
Ed25519 is the current gold standard for SSH keys. It’s faster than RSA, uses smaller key sizes, and is resistant to side-channel attacks. If you’re still using RSA 2048, this is the upgrade you didn’t know you needed.
When prompted, save the key to the default location (~/.ssh/id_ed25519) and set a passphrase. The passphrase encrypts the key on disk — if someone steals your laptop, they still can’t use your key without it.
Now copy the public key to your server:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip
This appends your public key to the server’s ~/.ssh/authorized_keys file. Test it by logging in — you should get in without a password prompt (but with your key passphrase if you set one).
Verify the key works:
ssh -i ~/.ssh/id_ed25519 user@your-server-ip "echo 'Key authentication working'"
Step 2: Disable Root Login
There’s no reason anyone should log in as root over SSH. Ever. Every automated scanner on the internet tries root@your-ip first — it’s the most targeted account on any Linux system.
Edit the SSH daemon configuration on your server:
sudo nano /etc/ssh/sshd_config
Find or add this line:
PermitRootLogin no
If the line already exists and is set to yes or prohibit-password, change it to no. If it’s commented out with a #, remove the comment and set it to no.
Save the file and restart SSH:
sudo systemctl restart sshd
Test this in a new terminal — try logging in as root. You should be rejected. If you can still log in as root, something else is overriding this setting (check /etc/ssh/sshd_config.d/ for drop-in configs).
Step 3: Change the Default SSH Port
Port 22 is the default. Every bot, script kiddie, and automated scanner targets port 22 first. Changing it won’t stop a determined attacker, but it eliminates 95% of the noise in your logs.
In /etc/ssh/sshd_config, find and change:
Port 2222
Pick any port above 1024 that isn’t already in use. Common choices: 2222, 8022, 2200. Avoid well-known ports like 80, 443, or 3306 — those attract attention too.
After restarting SSH, update your local SSH config to use the new port. On your local machine, edit ~/.ssh/config:
Host myserver
HostName your-server-ip
User your-username
Port 2222
IdentityFile ~/.ssh/id_ed25519
Now ssh myserver works without remembering the port. This config file is one of the most underused tools in a developer’s toolkit — it turns a 40-character command into a single word.
Before you restart SSH: Make sure your firewall allows the new port. On Ubuntu with UFW:
sudo ufw allow 2222/tcp
sudo ufw reload
Then restart SSH. If you forget the firewall step, you’ll lock yourself out.
Step 4: Set Up fail2ban to Block Brute Force Attacks
Even with keys-only authentication, bots will keep hammering your SSH port. fail2ban watches log files and bans IPs that show malicious behavior — too many failed login attempts, for example.
Install it:
sudo apt install fail2ban -y
Create a local configuration file (never edit fail2ban.conf directly — updates will overwrite your changes):
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Add or modify the SSH jail:
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
This bans any IP that fails SSH authentication 3 times within 10 minutes, for a full hour. For a production server, those numbers are conservative — you can tighten them further once you’ve confirmed legitimate traffic patterns.
Enable and start the service:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Check that it’s running:
sudo fail2ban-client status sshd
You’ll see the current number of banned IPs and the filter status. After a few days, check again — you’ll likely be surprised how many IPs have been banned.
Step 5: Restrict Access to Specific Users
By default, any user with a valid key can log in. If your server has multiple users, that’s a wider attack surface than necessary.
In /etc/ssh/sshd_config, add:
AllowUsers deploy admin
This restricts SSH access to only the deploy and admin users. Everyone else — including service accounts, test users, and anyone you forgot about — gets denied at the door.
Be careful with this one. If you add a user to AllowUsers who doesn’t have a key set up yet, you might lock them out. Always verify key access for each user before restricting.
Step 6: Disable Password Authentication Entirely
This is the big one. If you’ve set up SSH keys in Step 1, you don’t need passwords at all. Disabling password authentication eliminates brute force attacks completely — there’s nothing to brute force.
In /etc/ssh/sshd_config:
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
Warning: Only do this AFTER you’ve confirmed SSH key authentication works. I cannot stress this enough. I’ve had colleagues lose access to production servers because they disabled passwords before testing their keys. Always, always test first.
Restart SSH and verify in a new terminal:
ssh myserver "echo 'Still connected'"
If that works, you’re golden. If it fails, fix your key setup before doing anything else.
Step 7: Configure Your SSH Client for Daily Use
The SSH client config file (~/.ssh/config) isn’t just for custom ports. Here’s a production-ready config that covers the essentials:
Host production
HostName 192.168.1.100
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
StrictHostKeyChecking yes
ServerAliveInterval 60
ServerAliveCountMax 3
The key additions here:
StrictHostKeyChecking yes— prevents man-in-the-middle attacks by verifying the server’s host key on every connectionServerAliveInterval 60— sends a keepalive packet every 60 seconds, preventing idle connections from droppingServerAliveCountMax 3— disconnects after 3 missed keepalives, so you don’t hang on a dead connection
You can set up multiple hosts in this file — one for each server you manage. Type ssh production instead of remembering IP addresses, ports, and usernames.
Verification Checklist
After making all seven changes, run through this verification:
# Test key authentication
ssh myserver "echo 'Key auth works'"
# Test root login is blocked
ssh root@your-server-ip -p 2222
# Should be denied
# Check fail2ban status
sudo fail2ban-client status sshd
# Review your config for any mistakes
sudo sshd -t
# No output = config is valid. Errors mean something is wrong.
The sshd -t command is your safety net. It tests the configuration file for syntax errors without restarting the service. Run it after every change to sshd_config.
Troubleshooting
“Permission denied (publickey)” — Your key isn’t on the server, or the permissions on ~/.ssh are wrong. Fix with:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Locked out after changing the port — If you changed the SSH port but forgot to update your firewall or client config, you’ll need console access (VPS provider’s web console, or physical access for bare metal) to fix it.
Config changes not taking effect — Check for drop-in files in /etc/ssh/sshd_config.d/. Modern Ubuntu versions use these to override the main config. A file in there might be undoing your changes.
fail2ban not banning anyone — Make sure the log path in your jail config matches your system’s actual auth log. On Ubuntu it’s /var/log/auth.log; on CentOS/RHEL it’s /var/log/secure.
The Bottom Line
Seven changes. Fifteen minutes. And your SSH server goes from “please hack me” to “not worth the effort.” None of these steps are exotic or complicated — they’re basic hygiene that most people skip because the default config “just works.”
But “just works” is exactly what attackers count on. The IronWorm incident proved that supply chain attacks exploit the gap between what works and what’s secure. SSH hardening closes that gap on the infrastructure side.
If you’re managing servers — whether for a government agency, a startup, or your personal projects — these aren’t optional anymore. They’re the minimum.