Servers & Hosting/Guide
SSH hardening: keys, sane defaults and fewer open doors
Most attacks on SSH are automated guessing. Keys, a small drop-in file and an allow list make that guessing pointless. Here are the settings and how to apply them without locking yourself out.
sha256("/servers/ssh-hardening/")Put any server on the internet and within minutes its SSH port receives login attempts from automated scanners. They try common user names and leaked passwords, all day long. You cannot stop the knocking, but you can make sure no knock ever works. OpenSSH is well-built software, and hardening it is mostly about switching off what you do not need and limiting who may connect.
Step 1: create an Ed25519 key and install it
keys are short, fast and supported by every current OpenSSH version. Create the key on your own computer, never on the server, and protect it with a passphrase. Then copy the public half to the server while password login still works. If your provider gave you only a root login, first create a personal user with sudo rights and test it.
# Create a key with a passphrase and a comment naming the device
ssh-keygen -t ed25519 -C "work-laptop"
# Copy the public key to the server (password login still works now)
ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@server.example.com
# Test: this must log you in with the key
ssh -i ~/.ssh/id_ed25519 admin@server.example.comThe last command should ask for the key's passphrase, not the server password. Only continue when it works for every admin account that needs access.
Step 2: write a drop-in configuration
Current Debian, Ubuntu, Rocky Linux and AlmaLinux releases read every file matching /etc/ssh/sshd_config.d/*.conf near the top of the main configuration. Use such a instead of editing sshd_config itself: your settings survive package updates and stay in one place.
One rule from the OpenSSH manual matters here. For most keywords the first value read wins, and the files are read in lexical order. A file named 10-hardening.conf therefore beats a 50-cloud-init.conf, which cloud-init creates on some images and which may switch password login back on. Because the allow list below refuses everyone outside one group, create that group and add yourself first.
# Confirm the drop-in directory is read (expect an Include line)
grep -i '^Include' /etc/ssh/sshd_config
# Create the login group and add every admin account to it
sudo groupadd sshusers
sudo usermod -aG sshusers admin# Keys only, no root login
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
# Only members of this group may log in
AllowGroups sshusers
# Fewer guesses per connection, less time to authenticate
MaxAuthTries 3
LoginGraceTime 30
# Switch off what you do not use
X11Forwarding noWhat the lines do:
PasswordAuthentication noandKbdInteractiveAuthentication noremove both ways of typing a password, so only keys are accepted.PermitRootLogin nomakes everyone log in as a named user and usesudo, which leaves a trail of who did what. The upstream default,prohibit-password, still allows root with a key.AllowGroups sshusersrefuses every account outside that group, including service accounts that should never log in interactively.MaxAuthTries 3ends a connection after three failed attempts instead of six, andLoginGraceTime 30gives a client 30 seconds instead of 120 to finish logging in.X11Forwarding noswitches off graphical forwarding, which servers rarely need and some distributions enable in their main file.
Every key your SSH agent offers counts as one attempt. If your agent holds many keys, point the client at the right one with IdentityFile and IdentitiesOnly yes in ~/.ssh/config, or the low MaxAuthTries value will refuse you before the right key is tried.
Leave ciphers and key exchange algorithms at their defaults. Current OpenSSH defaults are strong, and recent versions already prefer a hybrid post-quantum key exchange. Lists copied from older hardening guides can quietly weaken that or lock out new clients.
Step 3: test before you reload
Check the syntax, look at the values the server will actually use, then reload. sshd -t prints nothing when the configuration is valid. sshd -T prints the effective settings, which shows at once whether another file overrides yours.
# Syntax check: no output means no errors
sudo sshd -t
# Show the effective values
sudo sshd -T | grep -Ei '^(passwordauthentication|permitrootlogin|allowgroups|maxauthtries)'
# Apply: the service is ssh on Debian and Ubuntu, sshd on Rocky Linux and AlmaLinux
sudo systemctl reload sshStep 4: rate limiting and firewall rules
Without passwords, guessing cannot succeed, but it still fills your logs and uses resources. Since version 9.8, OpenSSH itself penalises addresses that repeatedly fail or misbehave, through PerSourcePenalties, which is on by default. Not every distribution ships 9.8 or later yet, so check the installed version with ssh -V on the server. Tools such as add bans at the firewall and can protect other services too.
[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1hThe systemd backend reads the journal, which matters on current Debian releases that no longer write /var/log/auth.log by default. Check with sudo fail2ban-client status sshd that the jail is running.
The strongest measure is to make SSH reachable only from addresses you trust: your office, a VPN or a . Use your provider's cloud firewall or the host firewall. Add the rule for your own address first, test it from a second session, and only then remove any broader rule.
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp
sudo ufw status numberedMoving SSH to another port reduces log noise from the simplest scanners, nothing more, because a full port scan finds the new port quickly. If you change it anyway, update the firewall, label the port for SELinux on Rocky Linux and AlmaLinux with semanage port -a -t ssh_port_t -p tcp plus the number, and on recent Ubuntu releases mind : run systemctl daemon-reload and restart ssh.socket, not just the service.
Step 5: keep OpenSSH updated and watch logins
OpenSSH has a good security record, but serious bugs do happen. In 2024 a race condition in the server, known as regreSSHion (CVE-2024-6387), could allow unauthenticated remote code execution on glibc-based Linux systems. Distributions shipped fixes quickly, so servers with automatic security updates were covered without anyone lifting a finger.
Look at who logs in. journalctl -u ssh (or -u sshd on the RHEL family) shows accepted and failed logins, and last lists recent sessions. Remove keys from authorized_keys when people leave or devices are lost, and review the file on every server once a quarter.
Keys used by scripts and backup jobs deserve extra limits. Options at the start of their line in authorized_keys can tie a key to one source address with from=, or switch off terminals and forwarding with restrict. A stolen automation key is then far less useful.
Before you close the first session
0 of 11 done
Your ticks stay in this browser.
You cannot stop the knocking, but you can make sure no knock ever works.
Sources and further reading
- OpenSSH manual: sshd_config(5)man.openbsd.org
- OpenSSH release notesopenssh.org
- Ubuntu Discourse: SSHd now uses socket-based activationdiscourse.ubuntu.com
General information, checked on 31 October 2026. Test changes on one system first and keep a way back. Found something outdated? Tell the editors.