SSH is the gateway to your Linux server. It is also the number one target for attackers: expose a server to the internet with port 22 open, and you will see the first brute force attempts within an hour. Automated bots, password dictionaries, credential stuffing… the background noise is constant.
The good news: hardening SSH requires neither a budget nor exotic tools. With a careful configuration of sshd_config and a few additional building blocks, you eliminate the vast majority of opportunistic attacks. Here are 10 practical steps, ranked by priority, with the exact directives to apply.
The classic trap: lockout. One mistake in sshd_config and you lose access to your own server. Golden rule: always keep an SSH session open while modifying the configuration, test the connection in a second terminal, and validate the syntax with sshd -t before any systemctl restart sshd. If possible, keep console access (KVM, cloud console) handy as well.
The 10 measures, in order
1. Replace passwords with ed25519 keys
This is the measure that changes everything. Passwords can be guessed, brute-forced, or reused. An ed25519 key (modern elliptic curve, fast, short keys) is beyond the reach of any dictionary attack. Generate your key pair on the client side, with a passphrase:
ssh-keygen -t ed25519 -a 100 -C "jean@poste-admin"
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected] The -a 100 flag increases the number of key derivation rounds for the passphrase: if your private key is stolen, it remains costly to crack. Prefer ed25519 over RSA: for equivalent security, keys are shorter and operations are faster. Once key authentication has been confirmed (from a second terminal!), disable password authentication:
# /etc/ssh/sshd_config
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no 2. Disable direct root login
The root account exists on every Linux server: it is the first username bots test. By disabling it, you force the attacker to guess both a username and a secret. Bonus: you gain true traceability, as every admin logs in with their own account then elevates their privileges via sudo.
# /etc/ssh/sshd_config
PermitRootLogin no If an automation tool requires root (backups, deployment), use PermitRootLogin prohibit-password with a dedicated, restricted key, never yes.
3. Restrict allowed accounts with AllowUsers
By default, any local account with a shell can attempt an SSH connection, including service accounts created by packages. Explicitly allowlist legitimate accounts:
# /etc/ssh/sshd_config
AllowUsers admin deploy
# Variante par groupe :
# AllowGroups ssh-users You can even combine user and source: AllowUsers [email protected].* only allows the admin account from your management network (a WireGuard VPN, for example).
4. Change the port... knowing its limitations
Moving SSH from port 22 to a non-standard port (such as 2222 or 49152) drastically reduces automated scan noise in your logs. But be realistic: it is noise reduction, not security. A full scan like nmap -p- -sV finds your port in minutes, and the SSH banner gives it away immediately. Consider this measure a way to make logs easier to read, never genuine protection.
# /etc/ssh/sshd_config
Port 49152
# N'oublie pas le pare-feu AVANT de redémarrer sshd :
ufw allow 49152/tcp
ufw delete allow 22/tcp # seulement après avoir validé la connexion 5. Install fail2ban to ban attackers
fail2ban reads authentication logs in real time and bans (via nftables/iptables) any IP address with repeated failures. Even with passwords disabled, it stops persistent scans and protects other exposed services.
apt install fail2ban
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 49152
maxretry = 3
findtime = 10m
bantime = 1h
# Bannissement progressif : double la durée à chaque récidive
bantime.increment = true Check that it is running with fail2ban-client status sshd: you will see the number of currently banned IPs. On an exposed server, expect dozens per day.
6. Add a second factor: TOTP MFA on SSH
For sensitive servers, combine the ed25519 key with a TOTP code (Google Authenticator, Aegis, FreeOTP...). Even if an admin's private key leaks, the attacker is stopped by the second factor.
apt install libpam-google-authenticator
google-authenticator # exécuté par chaque utilisateur, génère le QR code
# /etc/pam.d/sshd — ajouter :
auth required pam_google_authenticator.so
# /etc/ssh/sshd_config
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive The AuthenticationMethods publickey,keyboard-interactive directive requires both factors in succession: key first, then the TOTP code. Always test in a second terminal before closing your session: this measure causes the most lockouts.
7. Display a legal banner
A pre-authentication warning banner stops no attacker, but it has genuine legal weight: it establishes that access is restricted and connections are logged, strengthening any case for prosecution (Article 323-1 of the French Criminal Code on fraudulent access to an automated data processing system).
# /etc/ssh/sshd_config
Banner /etc/ssh/banner.txt # /etc/ssh/banner.txt
*** ACCES RESTREINT ***
Ce systeme est reserve aux utilisateurs autorises.
Toute connexion est journalisee. Tout acces non autorise
est passible de poursuites (art. 323-1 et s. du Code penal). 8. Reduce attack surface: timeouts, attempts, unnecessary features
Every option enabled by default is a potential attack surface. Disable whatever you do not use and limit windows of opportunity:
# /etc/ssh/sshd_config
MaxAuthTries 3 # 3 essais max par connexion
LoginGraceTime 30 # 30 s pour s'authentifier, sinon déconnexion
MaxSessions 4
ClientAliveInterval 300 # déconnecte les sessions mortes
ClientAliveCountMax 2
X11Forwarding no # inutile sur un serveur headless
AllowAgentForwarding no # sauf besoin réel (rebond contrôlé)
AllowTcpForwarding no # bloque les tunnels pivots
PermitTunnel no Be careful with AllowTcpForwarding no if you use SSH as a bastion or for legitimate tunnels (such as access to an internal database): apply it on a per-user basis instead using a Match User block.
9. Regularly audit auth.log
Hardening without monitoring is like driving without a dashboard. The /var/log/auth.log file (or journalctl -u ssh on recent systemd systems) reveals everything: who logged in, from where, and who failed.
# Connexions réussies
grep "Accepted" /var/log/auth.log
# Échecs et comptes testés par les bots
grep "Failed password\|Invalid user" /var/log/auth.log \
| awk '{print $NF}' | sort | uniq -c | sort -rn | head
# Top 10 des IP attaquantes
grep "Failed" /var/log/auth.log \
| grep -oE "from [0-9.]+" | sort | uniq -c | sort -rn | head Across an estate of multiple machines, centralise these logs (rsyslog, Wazuh, or any SIEM): a successful login from an unusual IP at 3 am should trigger an alert, not be discovered three weeks later.
10. Keep OpenSSH up to date and check your config
Critical SSH vulnerabilities do exist: regreSSHion (CVE-2024-6387) reminded us in 2024 that even OpenSSH is not infallible. Enable automatic security updates (unattended-upgrades on Debian/Ubuntu) and make a habit of validating every change:
# Vérifier la syntaxe AVANT de redémarrer
sshd -t
# Voir la configuration effective (defaults inclus)
sshd -T | grep -Ei "permitrootlogin|passwordauth|port|allowusers"
# Appliquer
systemctl restart sshd Tools like ssh-audit rate your key exchange, cipher, and MAC algorithms in a single command, alongside recommendations for phasing out obsolete suites.
Summary: a hardened, commented sshd_config
Here is the combination of the previous measures, ready to adapt to your context:
# /etc/ssh/sshd_config — configuration durcie
Port 49152 # réduction de bruit (pas une protection)
Protocol 2
# --- Authentification ---
PermitRootLogin no # jamais root en direct
PubkeyAuthentication yes
PasswordAuthentication no # clés ed25519 uniquement
PermitEmptyPasswords no
KbdInteractiveAuthentication yes # requis pour le TOTP
AuthenticationMethods publickey,keyboard-interactive
AllowUsers admin deploy # whitelist explicite
MaxAuthTries 3
LoginGraceTime 30
# --- Surface d'attaque ---
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no
ClientAliveInterval 300
ClientAliveCountMax 2
# --- Légal & logs ---
Banner /etc/ssh/banner.txt
LogLevel VERBOSE # journalise les empreintes de clés Where to start?
If you only take away three actions today: switch to ed25519 keys and disable passwords (measure 1), disable root login (measure 2), and install fail2ban (measure 5). These three neutralise the overwhelming majority of real-world attacks observed on an exposed server. The remaining measures can then be added in layers depending on the machine's sensitivity: this is precisely the principle of defence in depth.
And adopt the professional reflex: every change to sshd_config should be made with a backup session open, a validation check using sshd -t, and an independent connection test before closing anything.