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.

High impact6 min readAdmins, DevelopersUpdated 31 October 2026

Fingerprint of this page: 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.

bashOn your own computer
# 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.com

The 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.

bashOn the server
# 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
ini/etc/ssh/sshd_config.d/10-hardening.conf
# 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 no

What the lines do:

  • PasswordAuthentication no and KbdInteractiveAuthentication no remove both ways of typing a password, so only keys are accepted.
  • PermitRootLogin no makes everyone log in as a named user and use sudo, which leaves a trail of who did what. The upstream default, prohibit-password, still allows root with a key.
  • AllowGroups sshusers refuses every account outside that group, including service accounts that should never log in interactively.
  • MaxAuthTries 3 ends a connection after three failed attempts instead of six, and LoginGraceTime 30 gives a client 30 seconds instead of 120 to finish logging in.
  • X11Forwarding no switches 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.

bashOn the server, in your existing session
# 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 ssh

Step 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.

ini/etc/fail2ban/jail.local
[sshd]
enabled  = true
backend  = systemd
maxretry = 5
findtime = 10m
bantime  = 1h

The 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.

bashufw on Debian and Ubuntu (203.0.113.10 stands for your address)
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp
sudo ufw status numbered

Moving 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

  1. OpenSSH manual: sshd_config(5)man.openbsd.org
  2. OpenSSH release notesopenssh.org
  3. 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.

FAQ

Questions readers ask

Is a passphrase on the key really necessary?

It protects you when the private key file is copied, for example from a stolen laptop or a leaked backup. With ssh-agent you type it once per session, so the cost is small. For automated jobs, use a separate key with restrictions set in authorized_keys.

Should I use AllowUsers or AllowGroups?

Both work. AllowGroups scales better, because you add or remove people through group membership instead of editing the SSH configuration. With either one, everyone not listed is refused, so add yourself before you reload.

Is fail2ban still needed when passwords are disabled?

It is not essential, because key-only login cannot be guessed. It reduces log noise and load, and it can protect other services on the same machine. On OpenSSH 9.8 and later, the built-in penalties already cover the basics.

What should I do if I am locked out?

Use your provider's web or serial console to log in locally, then remove or fix the drop-in file and reload SSH. If no console exists, most providers offer a rescue system that mounts your disk. Knowing which option you have before you start is part of the job.

Keep reading

More from Servers & Hosting