2026-07-08 · Team Fenrir
SSH hardening on Linux: what actually matters
If your Linux server is exposed to the internet, its SSH is already being hammered: credential brute forcing is so widespread that MITRE catalogues it in four distinct variants (T1110), and SSH is one of its favourite targets. The good news: four interventions — keys instead of passwords, a fixed sshd_config, restricted access, fail2ban — shut the door on almost all of these attacks. Here is the checklist, command by command.
Why SSH, specifically?
Because it is the administration door: whoever gets in through it has (or can obtain) full control of the machine. MITRE ATT&CK catalogues credential guessing as T1110 (Brute Force) — password guessing, password spraying, credential stuffing — and the use of SSH to move between compromised machines as T1021.004 (Remote Services: SSH). Translated: attackers use SSH both to get in and to spread out once inside.
The weak point is not the protocol — it is solid — but the configuration: weak passwords, exposed root, forgotten keys. All things you can fix in an afternoon.
How do I switch from passwords to keys?
It is the single most effective intervention. A password can be guessed by trial; an ed25519 key cannot. Three commands:
# 1. Generate the key pair (on YOUR computer, not on the server)
ssh-keygen -t ed25519 -a 64
# 2. Install the public key on the server
ssh-copy-id user@server
# 3. Verify you can log in WITHOUT a password before proceeding
ssh user@server
Only when step 3 works, disable passwords. Mind the default: in the official OpenSSH manual, PasswordAuthentication defaults to yes — if you don’t turn it off, brute force remains possible. In /etc/ssh/sshd_config:
PasswordAuthentication no
KbdInteractiveAuthentication no
Then systemctl reload sshd — keeping your current session open: if you got something wrong, you still have a shell to fix it.
What do I fix in sshd_config?
The directives that matter, with the real defaults from the manual (many online guides get them wrong):
PermitRootLogin no— the OpenSSH default isprohibit-password: root can already only log in with a key, not a password. Setting it tonois better still: you log in as a named user and elevate withsudo, so every action in the logs has a name attached.MaxAuthTries 3— the default is 6 attempts per connection. Three are plenty for anyone holding the right key.LoginGraceTime 30— the default is 120 seconds to complete a login. Thirty are enough, and they cut down connections parked by scanners.AllowUsers mario deploy(orAllowGroups ssh-users) — by default any system user may log in. Explicitly listing who can enter cuts out service accounts and forgotten users.ClientAliveInterval 300+ClientAliveCountMax 2— closes dead sessions instead of leaving them hanging.
What about the port? Moving it off 22 reduces noise, not risk: mass scanners stop, a targeted attacker finds it with a port scan. Fine to do for log hygiene, never as a security measure.
How do I restrict who can connect?
Two complementary levels:
- At the network level — if administration happens from known IPs (office, VPN), the firewall is the first line:
ufw allow from 203.0.113.10 to any port 22and nothing else. An SSH that cannot be reached cannot be attacked. - At the sshd level —
AllowUsers/AllowGroupsas above, andMatchblocks for targeted rules (for example: thedeployuser may only log in from the CI’s IP, only with a key, with no interactive shell).
Is fail2ban still worth it?
Yes, as an additional layer. fail2ban does one thing and does it well: it bans IPs that accumulate authentication errors by reading the logs in real time. With passwords disabled, classic brute force is already sterile, but fail2ban keeps the logs clean, slows down reconnaissance and also protects the other exposed services.
apt install fail2ban # Debian/Ubuntu
systemctl enable --now fail2ban
fail2ban-client status sshd # active jail, banned IPs
Its structural limit: it is reactive and per-IP. A distributed attack across thousands of IPs — a few attempts each — stays under the ban threshold. That is why it is the extra layer, not the foundation.
And the keys — who manages them?
The flip side of keys: they accumulate. NIST dedicated an entire report to the problem (IR 7966): in real infrastructures SSH keys proliferate with no inventory and no expiry — employees who left years ago still present in some authorized_keys, automation keys with more privileges than needed. Hardening does not end with configuration: it needs ongoing hygiene.
In practice, every quarter:
# Who can log into this machine?
for u in $(cut -d: -f1 /etc/passwd); do
f="/home/$u/.ssh/authorized_keys"
[ -s "$f" ] && echo "== $u" && wc -l < "$f"
done
Any key you cannot attribute must go. Any automation key must be constrained (command=, from= in authorized_keys) to the minimum it needs to do.
How do I verify the configuration holds?
Don’t trust the file: ask sshd what it is actually applying (includes, overrides and Match blocks included):
sshd -T | grep -Ei 'passwordauth|permitroot|maxauthtries|allowusers|kbdinteractive'
If the output says passwordauthentication no and permitrootlogin no, the door is genuinely shut. Then look at the logs — that is where you see the siege:
journalctl -u ssh --since today | grep -c 'Failed\|Invalid'
On an exposed server, that number will not be zero. That is fine: the goal of hardening is not to stop the attempts, it is to make every one of them useless.
Why does monitoring matter as much as hardening?
Because hardening is a snapshot, and a server is a film. Today’s perfect configuration will erode: a colleague re-enables passwords “for five minutes”, a provisioning tool rewrites sshd_config, one key too many lands in authorized_keys. And the 2 a.m. brute force does not email you on its own.
You need something that watches continuously: authentication logs, changes to sshd_config and to authorized keys, anomalous access patterns — and that acts when needed, not on Monday morning. That is exactly the job of Fenrir SOC: it correlates SSH logs with its other monitors, classifies the attack and, above the confidence threshold, blocks it on its own in under a second. You sign off this page’s checklist once; it defends it every night.
Frequently asked questions
Are SSH keys better than passwords?▾
Yes. An ed25519 key cannot be guessed by trial: brute force works on passwords, not on keys. The full switch takes two moves: install the key with ssh-copy-id, then set PasswordAuthentication no (the OpenSSH default is yes).
Should I set PermitRootLogin no?▾
The OpenSSH default is already prohibit-password (root can only log in with a key). Setting it to no is cleaner anyway: it forces logging in as a named user and elevating with sudo, so every action in the logs has a name attached.
Does changing the SSH port improve security?▾
It reduces automated scanner noise in your logs, not real security: a targeted attacker finds the port with a scan. Do it if you like, but after keys, PasswordAuthentication no and fail2ban — never instead of them.
Is fail2ban enough against brute force?▾
It helps: it bans IPs that accumulate authentication errors and cuts the noise. But it is reactive and per-IP: a distributed attack gets around it. The real defence is eliminating passwords; fail2ban is the extra layer, not the foundation.