Explore top 10 tips to secure your open-source projects now. Read More
×
Spin up a fresh Linux VPS with default settings and check /var/log/auth.log ninety seconds later. There will already be failed login attempts — not dozens, hundreds, sometimes before the deployment script has even finished running.
Automated bots scan the entire IPv4 address space non-stop, and port 22 with password authentication open is exactly what they're looking for. Most cloud providers have seen enough of this that a new instance gets its first probe within a minute of going live.
Security research tracking exposed Linux endpoints found that 89% of Linux endpoint attack behaviors in 2025 involved brute force or credential stuffing against SSH. Eighty-nine percent. In early 2026, the SSHStalker botnet — discovered by Flare Systems via SSH honeypot — had already racked up nearly 7,000 compromised systems by the end of January, mostly cloud servers, not through any sophisticated exploit but through weak or default credentials. Once inside, the malware dropped an SSH key, then immediately started scanning for more victims on port 22. A DShield sensor from around the same period documented the full cycle at under four seconds: first connection to complete botnet enrollment.
The attacks aren't sophisticated. Default configurations keep making sophistication unnecessary.
Disabling password authentication is the change with the most immediate impact. Do it first, before touching anything else. Once it's in place, the entire brute-force and credential-stuffing attack surface disappears — bots can hammer port 22 as long as they want, and without the private key they're simply not getting in. 
For new key pairs, Ed25519 is the right choice. Faster than RSA, smaller, stronger at comparable performance:
ssh-keygen -t ed25519 -C "production-server" -f ~/.ssh/id_ed25519
Copy the public key to the server, then — and this step matters more than it sounds — open a second terminal and confirm the key-based login works from that second session before touching anything in the config. Don't close the existing session until you've confirmed a fresh connection succeeds. Locking yourself out of a remote server mid-hardening is the most common way this process goes sideways, and it's entirely avoidable:
# /etc/ssh/sshd_config
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
In environments where key management is taken seriously — and it should be — rotating keys every six months is worth the friction. It sounds excessive until a former employee still has valid credentials six months after leaving, or a key gets exfiltrated as part of a broader compromise nobody caught at the time.
A handful of changes, none of them complicated, collectively close off real attack surface. None should require debate.
Disable direct root login. There's no legitimate operational need for it — log in as a regular user and escalate with sudo when needed. And it adds a layer: an attacker who gets a key still needs to figure out which account has elevated access:
PermitRootLogin no
Explicit allowlists for SSH access so that any system accounts created after this point don't automatically inherit login capability:
AllowUsers deploy monitoring-user
Default values for authentication attempts and connection grace periods are far too generous for anything internet-facing:
MaxAuthTries 3
LoginGraceTime 20
MaxStartups 10:30:60
LoginGraceTime 20 drops connections that haven't authenticated in 20 seconds flat. MaxStartups 10:30:60 starts throttling unauthenticated connections at 10 pending and drops them aggressively past 60 — this directly limits parallel brute-force attempts trying to flood the authentication pipeline. Add idle session timeouts too, since forgotten open sessions are their own category of risk:
ClientAliveInterval 300
ClientAliveCountMax 0
On the port change. Moving SSH off port 22 won't stop a targeted attacker — a basic port scan finds it immediately. But it does eliminate the overwhelming majority of automated scanning traffic, since most botnets only ever target port 22. Logs get dramatically cleaner, and on production systems where log analysis is part of the daily workflow, that reduction in noise has genuine operational value. Worth doing, as long as nobody confuses it with a security control:
Port 2222
Key pairs are fine when you've got five servers. At fifty, the model breaks. You're distributing public keys to every host, rotating them manually, and inevitably inheriting authorized_keys files full of entries from people who left six months ago — nobody owns the cleanup, and stale access just sits there. 
The fix is treating SSH like PKI: a central Certificate Authority signs user keys, and every server trusts the CA instead of tracking individual keys. One revocation point. No per-host cleanup:
# Generate the CA key pair — store this offline or in a hardware security module
ssh-keygen -t ed25519 -f ~/.ssh/ssh_ca -C "production-ca"
# Sign a user's public key with a 24-hour validity window
ssh-keygen -s ~/.ssh/ssh_ca -I "username@prod" -n deploy -V +24h ~/.ssh/id_ed25519.pub
On each server, trust the CA — not individual keys:
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ssh_ca.pub
Issue certificates with short lifetimes — 24 hours works well — and a stolen key becomes useless overnight without touching a single host. The stale access problem that haunts larger fleets mostly disappears on its own.
Exposing SSH directly on every server multiplies attack surface with every machine you add. The cleaner model: a single hardened bastion host — the only machine with port 22 reachable externally — with all other servers restricted to accepting connections only from the bastion's IP at the firewall level.
All hardening effort concentrates on one machine. Internal servers can drop everything that doesn't come from the bastion. And SSH's ProxyJump makes the setup seamless:
# ~/.ssh/config
Host internal-server
HostName 10.0.1.50
User deploy
ProxyJump bastion.example.com
ssh internal-server tunnels through the bastion automatically. Every lateral connection gets logged there. Combined with SSH certificates, you get one enforcement point for the entire fleet.
Key-only authentication largely kills the brute-force problem. Fail2ban still earns its place — misconfigured clients, edge cases, environments where password auth genuinely can't be fully disabled for one reason or another. A baseline configuration that bans IPs for 24 hours after three failed attempts:
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 86400
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
maxretry = 3
Adjust the port value to match whatever is set in sshd_config. After any configuration changes, restart and verify:
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
For privileged access workstations or jump servers where a stolen key alone would be an unacceptable outcome, stack TOTP on top. Twenty minutes of setup, and a compromised private key by itself stops working:
# /etc/ssh/sshd_config
AuthenticationMethods publickey,keyboard-interactive
Getting the configuration right is one half of this. Actually reading the logs is the other — and it gets underestimated consistently. 
SSH authentication logs are genuinely informative when you read them with intent rather than just watching for volume spikes. Repeated failures from a single source followed by a success: credential stuffing, worth investigating regardless of the account. Authentication from an IP that's never appeared before on an account with established connection history: worth a second look. More telling than either: a privileged account suddenly connecting from somewhere it never has before. Or a service account that's never had an interactive session showing up with one.
auditd extends monitoring past what SSH logs capture:
auditctl -a always,exit -F arch=b64 -S execve -F euid=0 -k root_commands
Every root command gets logged with a tag for easy filtering. Cross-reference with SSH auth events and you get a full timeline from initial login through command execution — which matters a lot during incident response when reconstruction is what you need.
SSHStalker reinforced something worth keeping in mind: compromised hosts don't just become victims, they become attack infrastructure. A server generating high outbound connection volumes to port 22 on non-private IP ranges is almost certainly already part of a scanning relay. Behavioral monitoring that flags that specific pattern catches it faster than manual log review ever will.
The DShield sensor data from early 2026: four seconds from first connection to fully enrolled botnet node on a default-credential system. Not a theoretical estimate — an actual observed result from a real internet-exposed VPS.
The full hardening process here takes under an hour on a fresh server. Disable passwords, deploy a key, tighten the config, install fail2ban. The gap between a default SSH setup and a properly hardened one isn't a project. It's an afternoon.
For the offensive perspective on SSH — how attackers enumerate configurations, harvest private keys from the filesystem, hijack agent sockets to move laterally without ever touching a key file, and use SSH tunneling to reach internal services — HackITA's SSH attack surface guide breaks down the techniques defenders need to understand.