If you run a server exposed to the Internet, there is one thing you should probably accept:
Your server is being scanned. Right now.
Not tomorrow. Not next week.
Right now.
Bots constantly crawl the Internet looking for exposed services: SSH, databases, admin panels, dashboards, forgotten development tools... anything with a listening port and a bad day ahead of it.
The moment your server exposes SSH to the public Internet, automated scanners can start hammering it with login attempts.
And here's the fun part:
You can see it for yourself.
Step 0: How bad is it?
On Debian or Ubuntu, run:
sudo journalctl -u ssh --since "-7 days" 2>/dev/null | grep -c "Failed password" \
|| sudo grep -c "Failed password" /var/log/auth.log
This counts failed password-based authentication attempts recorded over the last seven days.
On two of my servers, I got:
43,000 and 31,000 attempts.
In one week.
Another server in the same discussion clocked in at almost 50,000.
At this point, there are two possible reactions:
- Panic.
- Check the logs before panicking.
I recommend option two.
The Internet is not hostile. It is merely extremely curious and very badly behaved.
These attempts are usually automated. Bots scan public IP ranges, find an open SSH service, and try common usernames and passwords.
They're not necessarily targeting you.
You're just another IP address on the menu.
And that leads to the first important lesson:
Your server doesn't need to be famous to be attacked. It only needs to be online.
First: check whether anyone actually got in
A huge number of failed attempts sounds scary.
But the number that really matters is different:
Were any logins successful?
Check your SSH logs:
sudo journalctl -u ssh --since "-7 days" | grep -E "Accepted|session opened"
Look for successful authentications and make sure you recognize them.
This distinction matters:
50,000 failed attempts do not automatically mean compromise.
One successful login from an unknown source might.
Don't count the zombies. Look for footprints.
Before you start adding security tools, understand what is actually happening.
You can also inspect the most active source IPs:
sudo journalctl -u ssh --since "-7 days" 2>/dev/null \
| grep "Failed password" \
| grep -oE 'from ([0-9]{1,3}\.){3}[0-9]{1,3}' \
| sort \
| uniq -c \
| sort -nr \
| head -20
And the usernames being attacked:
sudo journalctl -u ssh --since "-7 days" 2>/dev/null \
| grep "Failed password" \
| sed -n 's/.*Failed password for \(invalid user \)\?\([^ ]*\).*/\2/p' \
| sort \
| uniq -c \
| sort -nr \
| head -20
You will probably see classics like:
root, admin, ubuntu, test, user, oracle.
The world's hackers may be sophisticated.
Their first choice of username often isn't.
This is the first real security move.
If you can use SSH keys, use SSH keys.
In /etc/ssh/sshd_config:
PasswordAuthentication no
Now SSH won't accept password-based authentication.
That's a massive improvement.
Why?
Because passwords can be:
- guessed
- brute-forced
- reused
- leaked
- phished
- shared
- written on a Post-it note under the keyboard
SSH keys solve a large part of that problem.
But there is one important detail:
Put a passphrase on your private key
A private key without a passphrase is basically a skeleton key sitting on your laptop.
If your machine gets compromised and the attacker steals that key, they may be able to use it to access your servers.
Use a strong passphrase.
Think of it as:
Something you have + something you know.
Or, in geek terms:
A private key without a passphrase is like putting a deadbolt on a door and leaving the key in the lock.
Next:
PermitRootLogin no
The root username is predictable.
Why make the attacker guess half the equation?
Use a normal account:
sudo adduser myuser
Then give it administrative privileges:
sudo usermod -aG sudo myuser
Connect with:
ssh myuser@server
And use sudo when you need elevated privileges.
You can also restrict which users are allowed to connect over SSH.
For example:
AllowUsers myuser alice bob
Or use a dedicated SSH group:
AllowGroups sshusers
Now your SSH service isn't just saying:
"Welcome, anyone with credentials."
It's saying:
"Welcome, these specific humans."
Much better.
This is where security tutorials quietly become horror stories.
You modify sshd_config.
You restart SSH.
And suddenly:
Welcome to the exciting world of your hosting provider's emergency console.
Before restarting SSH, validate the configuration:
sudo sshd -t
Keep your existing SSH session open.
Open a second terminal.
Test the new configuration there.
Only then restart the service:
sudo systemctl restart ssh
or:
sudo systemctl restart sshd
The golden rule:
Never close the only working SSH session before testing the new one.
Your future self will thank you.
Probably with coffee.
4. Now ask the uncomfortable question
At this point, SSH is much safer.
But there is a bigger question:
Why is SSH exposed to the entire Internet in the first place?
This is the question that changes the whole strategy.
Because securing an exposed service is one thing.
Not exposing it is another.
5. The best SSH hardening trick: don't expose SSH
If you don't need public SSH access, don't publish it.
Put SSH behind a private network or VPN using something like:
- WireGuard
- Tailscale
- another private overlay network
Your architecture becomes:

Instead of:
That's a fundamentally different security model.
Your web application may need to be public.
Your SSH service probably doesn't.
Users need your website. They don't need your shell.
Or, more quotably:
Don't build a stronger lock for a door that shouldn't be on the sidewalk.
6. Firewall: decide who gets to knock
If SSH must remain exposed, restrict it at the network level.
The ideal rule is simple:
Known IPs → ACCEPT
Everyone else → DROP
If you have a stable public IP at home or at work, allow only that address.
Better still, use your cloud provider's firewall or security group whenever possible.
This gives you an important layer before SSH itself sees the connection.
That's a key distinction.
A firewall can stop traffic from reaching SSH at all.
Fail2Ban reacts after the connection has already arrived.
7. Fail2Ban: useful, but not your religion
Now we can talk about Fail2Ban.
Fail2Ban monitors logs and temporarily blocks IP addresses that generate too many failed authentication attempts.
It is useful.
Very useful, in fact.
But it is often treated like a magical anti-hacker shield.
It isn't.
Think of Fail2Ban as a bouncer.
The club still exists.
The doors are still visible.
People still reach the entrance.
The bouncer simply says:
"You've tried 47 times. Please leave."
Fail2Ban is a good additional layer for exposed services.
It is not a replacement for:
- SSH keys
PasswordAuthentication noPermitRootLogin no- firewall rules
- network isolation
The hierarchy matters.
Don't use a bigger bouncer to compensate for a front door made of cardboard.
8. Should you change SSH's port?
Ah yes.
The sacred ritual of SSH hardening:
22 → 2222
Does it help?
Yes.
Is it real security?
Not really.
Changing the port can dramatically reduce the amount of automated noise generated by simplistic scanners.
Your logs may become much quieter.
That's nice.
But anyone performing a broader port scan can discover the new port.
So:
Port 22 → 2222
does not magically become:
Safe → Very Safe™
It becomes:
Noisy → Less Noisy
That's useful, but it's a different thing.
Changing the port is noise reduction, not a security strategy.
If you have to choose between changing the port and disabling password authentication:
disable passwords.
If you have to choose between changing the port and putting SSH behind a VPN:
use the VPN.
Move the port if you want.
Just don't confuse obscurity with security.
9. MFA: another layer, not a shortcut
Multi-factor authentication can add another layer to SSH.
Depending on your environment, you may use:
- SSH keys
- hardware security keys
- TOTP
- PAM-based MFA
- identity-aware access systems
That's great.
But don't use MFA as an excuse to keep the rest of your setup weak.
A secure architecture still starts with:
keys → no passwords → no root → restricted network access
Then you add MFA where it makes sense.
Security is an onion.
Unfortunately, unlike onions, it doesn't make your attacks cry.
10. Keep the operating system boring
Security people love exciting technology.
Attackers love unpatched software.
These two groups have very different definitions of "fun."
Keep Debian, Ubuntu, OpenSSH, the kernel, and your exposed applications up to date.
On Debian/Ubuntu, unattended-upgrades can automate certain security updates.
That's useful, but production systems still need:
- backups
- monitoring
- testing
- rollback or recovery procedures
The most beautifully hardened SSH server in the world is still vulnerable if the underlying OS is ancient.
The strongest lock in the world won't help if the wall is made of Windows 95.
11. What about geo-blocking?
Blocking traffic from countries you don't operate in can reduce noise.
It can be useful.
But don't confuse it with a hard security boundary.
Attackers can use:
- VPNs
- proxies
- cloud instances
- compromised machines
Geographic origin is not identity.
Use geo-blocking as an optimization, not as your foundation.
12. Port knocking, IP blacklists and other wizardry
There are plenty of clever techniques around SSH:
Port knocking
Keep SSH hidden until a specific packet sequence arrives.
Interesting.
Sometimes useful.
Not a substitute for proper authentication and network controls.
Permanent IP blacklists
You can manually ban every IP that annoys you.
This sounds productive.
Until you realize there are millions of IP addresses and approximately three billion ways to generate more.
You are not going to manually blacklist the Internet.
Rootkit scanners
Tools such as rkhunter can provide additional monitoring.
Again: useful as a layer, not a replacement for fundamentals.
The pattern should be obvious by now:
Defense in depth. Not security-by-collection-of-random-tools.
13. What actually matters?
Here's the hierarchy I would use.
| Measure | Reduces noise | Improves security |
|---|---|---|
| Change SSH port | ✅ | ⚠️ Limited |
| Fail2Ban | ✅ | ✅ |
| SSH keys | — | ✅✅ |
| Key + passphrase | — | ✅✅ |
| Disable passwords | — | ✅✅ |
| Disable root login | — | ✅ |
AllowUsers / AllowGroups
|
— | ✅ |
| Firewall / IP allowlist | ✅✅ | ✅✅ |
| VPN / private network | ✅✅ | ✅✅ |
| MFA | — | ✅ |
| Geo-blocking | ✅ | ⚠️ Limited |
The pattern is pretty clear.
Some controls make your logs prettier.
Others actually reduce your attack surface.
Those are not the same thing.
A quiet log is not the same as a secure server.
14. The 10-minute SSH survival plan
You've just discovered 40,000 failed SSH attempts.
What should you do?
Do this, in order.
1. Check for successful logins
sudo journalctl -u ssh --since "-7 days" | grep -E "Accepted|session opened"
Investigate anything you don't recognize.
2. Make sure you have a working SSH key
Generate one if necessary:
ssh-keygen -t ed25519
Use a passphrase.
3. Disable password authentication
PasswordAuthentication no
4. Disable direct root login
PermitRootLogin no
5. Restrict SSH users
For example:
AllowUsers myuser
6. Validate the SSH configuration
sudo sshd -t
7. Test a second SSH connection
Do this before closing your current session.
8. Put SSH behind a firewall or VPN
Preferably both.
9. Add Fail2Ban if you still expose SSH
Good extra layer.
Not your entire strategy.
10. Change the port if you want less noise
Optional.
Not essential.
15. The architecture I would aim for
For a modern setup, the goal is simple:

The public Internet reaches the services that actually need to be public.
Administration stays private.
That's a much cleaner security model than endlessly hardening an SSH daemon that is exposed to every scanner on Earth.
16. The survival checklist
Essential
- [ ] Use SSH keys
- [ ] Protect private keys with passphrases
- [ ] Disable password authentication
- [ ] Disable direct root login
- [ ] Restrict SSH users with
AllowUsersorAllowGroups - [ ] Keep the OS and OpenSSH up to date
- [ ] Use a firewall
Better
- [ ] Restrict SSH to known IPs
- [ ] Put SSH behind WireGuard, Tailscale, or another private network
- [ ] Close public SSH completely when possible
- [ ] Add MFA where appropriate
Optional
- [ ] Fail2Ban
- [ ] Change the SSH port
- [ ] Geo-blocking
- [ ] Port knocking
- [ ] Additional host monitoring
And yes, there is a hierarchy here.
Changing port 22 is not more important than disabling passwords.
Fail2Ban is not more important than network isolation.
A fancy security stack is not more important than basic configuration.
The final rule: stop defending the wrong thing
The scary part about those 43,000 login attempts isn't really the number.
It's what the number teaches you.
The Internet is constantly probing anything you expose.
You can't stop the scanning.
You don't need to.
Your job is to make the scanning useless.
Start with the boring stuff:
SSH keys.
Passphrases.
No password authentication.
No root login.
Least privilege.
Firewall.
Then ask the most important question of all:
Does SSH need to be public at all?
If the answer is no, put it behind a VPN and close the door.
Because the strongest SSH security control isn't a clever configuration.
It's not Fail2Ban.
It's not port 2222.
It's not a 300-line firewall ruleset.
Actually, wait. Let me put it differently. The most effective protection isn't any of those tools. It's this:
If attackers can't reach the service, they can't brute-force the service.
Or, if you prefer the geek version:
You can't pwn what doesn't have a route.
And remember:
The goal isn't to make your server invisible.
The goal is to make it boring.
Extremely boring.
Because in infrastructure security, boring is beautiful.
Top comments (0)