Hardening SSH
SSH is the front door on nearly every Linux host, and it is continuously scanned and brute-forced from the moment a host gets a public IP. Most of the work of securing it is removing targets rather than defending them: an account that cannot be logged into cannot be guessed.
Work through the controls in order. The first two close the large majority of real-world exposure; the rest narrow what’s left.
10 controls
- Severity: critical
- Severity: critical
- Severity: high
- Severity: high
- Severity: medium
- Severity: low
- Severity: medium
- Severity: low
- Severity: low
- Severity: medium
Before you start
Section titled “Before you start”Every control below changes how you authenticate. Two rules make the whole list safe to apply:
- Keep your current session open while you work. If a change locks you out, an already-authenticated session is the way back in.
- Test from a second terminal before closing the first. Confirm you can still log in, then close.
Without console or out-of-band access, an SSH misconfiguration on a remote host is not recoverable.
Where the config lives
Section titled “Where the config lives”Server-side settings are in /etc/ssh/sshd_config. Many distributions also
read drop-in files from /etc/ssh/sshd_config.d/*.conf, and the first
matching directive wins — a setting in a drop-in can silently override the
one you just edited in the main file. This is why every control here verifies
against the running daemon rather than the file you edited.
Reloading, and the Ubuntu socket exception
Section titled “Reloading, and the Ubuntu socket exception”Most controls here take effect on reload:
sudo systemctl reload ssh # Debian, Ubuntusudo systemctl reload sshd # RHEL, Rocky, Alma, FedoraUbuntu 22.10 introduced systemd socket activation for SSH, where
ssh.socket owns the listener and spawns the daemon per connection. Check which
model you are on:
systemctl is-enabled ssh.socketIf that returns enabled, authentication settings still apply to new
connections as normal — but anything touching the listener (Port,
ListenAddress) is owned by the socket, not sshd_config, and needs
systemctl daemon-reload && systemctl restart ssh.socket. See
Firewall the SSH port for the details.
Always test with sudo sshd -t before reloading. It catches syntax errors while
you still have a working daemon.