SSH Hardening Best Practices
Table of Contents
SSH is the front door to almost every Ubuntu VPS. WireGuard may carry your user traffic, but operators still land on the host through OpenSSH. Hardening SSH means concrete sshd_config settings, key management discipline, and a host checklist that you apply before the box is interesting to attackers—not after the auth log fills with noise.
This guide focuses on OpenSSH server configuration on Ubuntu 22.04/24.04 LTS: what to set, what to verify, and how to avoid locking yourself out. Pair it with UFW and provider firewalls; SSH hardening does not replace network policy.
Keep a second active SSH session open for every reload until you confirm a fresh login works.
What You'll Learn
- A safe workflow for editing and validating
sshd_config - Key-only authentication and root login controls
- User and group restrictions (
AllowUsers/AllowGroups) - Session, auth try, and cryptographic algorithm hardening
- Host-level checklist items beyond sshd itself
- Common lockout causes and recovery approach
Safe Edit Workflow
Ubuntu splits config across /etc/ssh/sshd_config and /etc/ssh/sshd_config.d/*.conf. Prefer a drop-in:
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
Validate before reload:
sudo sshd -t
sudo systemctl reload ssh
On Ubuntu, the service name is typically ssh (not sshd). Check:
systemctl status ssh --no-pager
If sshd -t reports errors, fix them before reload. A bad config can refuse restart on reboot even if a reload seemed fine—test deliberately.
1. Key-Only Authentication
Generate an ed25519 key on your admin workstation:
ssh-keygen -t ed25519 -a 100 -C 'you@admin'
Install the public key in ~/.ssh/authorized_keys for the admin user (mode 600 on the file, 700 on .ssh).
In the hardening drop-in:
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
On older snippets you may still see ChallengeResponseAuthentication; on modern OpenSSH prefer KbdInteractiveAuthentication.
Verify from a new session before closing the old one:
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no deploy@SERVER
That attempt should fail. Your key-based login should succeed.
2. Disable Root Login
PermitRootLogin no
Alternatives:
prohibit-password— root key-only (still better to use a named user + sudo)no— recommended default for operator hosts
Ensure your sudo user works first. Cloud images that only inject root keys need migration:
sudo rsync --archive --chown=deploy:deploy /root/.ssh/ /home/deploy/.ssh/
3. Restrict Who Can Log In
AllowUsers deploy
Or:
AllowGroups sshadmins
Create the group and add users deliberately. AllowUsers is explicit and easy to audit. Do not combine conflicting Allow/Deny directives without reading sshd_config(5).
Deny empty passwords:
PermitEmptyPasswords no
4. Limit Auth Noise and Connection Abuse
MaxAuthTries 3
MaxSessions 10
LoginGraceTime 30
MaxStartups 10:30:60
PerSourceMaxStartups 5
Notes:
MaxAuthTriescaps attempts per connectionLoginGraceTimedrops slow authenticating connectionsMaxStartupsthrottles unauthenticated concurrent connections (useful under scan floods)- Exact
PerSourceMaxStartupssupport depends on OpenSSH version—sshd -Tshows effective values
5. Disable Features You Do Not Need
For a typical VPN/management VPS:
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
PermitTunnel no
DebianBanner no
If you rely on SSH local forwarding for a specific admin workflow, leave AllowTcpForwarding enabled only for that need—or use a Match block:
AllowTcpForwarding no
Match User deploy
AllowTcpForwarding yes
6. Modern Cryptography
Query effective algorithms:
sudo sshd -T | egrep '^(ciphers|macs|kexalgorithms|hostbasedacceptedalgorithms|pubkeyacceptedalgorithms)'
Prefer modern defaults from Ubuntu’s OpenSSH package. If you tighten explicitly, use current Mozilla or distribution guidance and re-test clients. Over-restricting breaks older jump hosts and embedded devices.
Host keys: keep ed25519 (and RSA only if you still need legacy clients). Remove unused host key types carefully—clients pin host keys.
ls /etc/ssh/ssh_host_*
7. Idle Timeouts (Optional but Useful)
ClientAliveInterval 300
ClientAliveCountMax 2
This drops dead sessions. Balance against long-running interactive work; VPN hosts often benefit from clearing idle admin sessions.
8. Banner and Legal Notice (Optional)
Banner /etc/ssh/banner
A banner is not a security control. Skip cute ASCII art on production auth paths if it confuses automation.
9. SFTP-Only Users (When Needed)
For a file drop account, use a Match block with ForceCommand internal-sftp and ChrootDirectory—test thoroughly. Most VPN servers should not expose extra SFTP users without a clear requirement.
Example Hardening Drop-In
/etc/ssh/sshd_config.d/99-hardening.conf:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitEmptyPasswords no
AllowUsers deploy
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
GatewayPorts no
PermitTunnel no
MaxAuthTries 3
LoginGraceTime 30
MaxSessions 10
ClientAliveInterval 300
ClientAliveCountMax 2
DebianBanner no
sudo sshd -t && sudo systemctl reload ssh
Host Hardening Checklist (Beyond sshd)
SSH config is necessary but not sufficient.
- Admin user with sudo; root SSH disabled
-
authorized_keysownership and modes correct - UFW allows SSH (optionally from admin IPs only)
- DigitalOcean Cloud Firewall mirrors SSH policy
- Fail2Ban
sshdjail enabled withignoreipfor your admin IP - Unattended security upgrades enabled
-
ss -tulpnshows SSH on expected address/port only - Provider console access documented for lockout recovery
- Operator keys inventoried; departed staff keys removed
- WireGuard or other services do not accidentally depend on insecure SSH forwarding
Listen Address Binding
By default OpenSSH listens on all interfaces. On a host with public and tunnel IPs, you can bind SSH to specific addresses after WireGuard is up:
ListenAddress 10.66.66.1
ListenAddress 127.0.0.1
Only do this when:
- WireGuard is enabled at boot and reliably returns
- You have DigitalOcean console access tested
- Cloud Firewall no longer needs public 22—or you accept console-only recovery
A safer intermediate step is leaving SSH on the public interface but allowlisting admin source IPs in UFW and Cloud Firewall. Binding to tunnel-only is a hardening step, not a day-one requirement.
authorized_keys Options Worth Using
You can restrict individual keys:
from="203.0.113.10,10.66.66.0/24",no-agent-forwarding,no-port-forwarding ssh-ed25519 AAAA... comment
Useful patterns:
from=— key only valid from certain source addressescommand=— force a single command (automation accounts)no-port-forwarding,no-agent-forwarding— reduce lateral movement usefulness if the key leaks
Keep human admin keys simple enough to operate under stress; reserve tight forced-commands for bots.
Config Snippets Hygiene
List drop-ins:
ls -la /etc/ssh/sshd_config.d/
Avoid duplicate contradictory settings across files—the first obtained value wins depending on directives; when confused, trust sshd -T over reading three files manually. Remove obsolete experimental drop-ins instead of commenting them forever.
SSH Client Config for Operators
Hardening the server does not excuse sloppy clients. On your laptop ~/.ssh/config:
Host vpn-droplet
HostName vpn.example.com
User deploy
IdentityFile ~/.ssh/id_ed25519_vps
IdentitiesOnly yes
IdentitiesOnly yes prevents the agent from offering many keys and tripping MaxAuthTries. That client-side habit pairs with server-side low try limits.
Changing the SSH Port
Port 2222
Update UFW and Cloud Firewall in the same window. Port changes reduce autoscanner noise; they are obscurity, not authentication. Prefer allowlists + keys over port hopping as your primary control.
After changing the port, update Fail2Ban port = (or use port = 2222) so bans still target the right listener. Test a new connection to the new port before closing your existing session.
Privileged Separation and Sandboxing
Ubuntu’s OpenSSH enables privilege separation and sandboxing by default. Do not disable UsePrivilegeSeparation (historical) or related protections based on outdated blog posts. When in doubt, compare against a fresh Ubuntu image’s defaults with sshd -T.
Match Blocks for Conditional Hardening
Use Match for exceptions without weakening the global policy:
PasswordAuthentication no
AllowTcpForwarding no
Match Address 10.66.66.0/24
AllowTcpForwarding yes
Match User backup
ForceCommand /usr/bin/rsync --server --sender -vlogDtpre.iLsfxC . /data/
AllowTcpForwarding no
X11Forwarding no
Match sections must sit at the end of the effective config (drop-in ordering matters). Validate with sshd -t and a non-production test user before relying on ForceCommand in production.
Hardware Keys and Agent Policy
Where budget allows, prefer FIDO2/sk-ssh-ed25519@openssh.com keys for human admins. They resist quiet copying of private key files. Combine with:
- Screen-lock policies on admin laptops
- No shared break-glass password accounts
- Short-lived access for contractors via temporary
authorized_keyslines with calendar removal
Document the recovery key ceremony: who holds a spare token, where the sealed envelope/password manager item lives, and how to use DigitalOcean console if tokens fail.
Best Practices
- Use ed25519 keys with a passphrase or an agent + OS keychain policy
- One key per operator (or per hardware token); revoke by deleting the public key line
- Validate with
sshd -tevery change - Prefer drop-in files over editing the entire distro
sshd_config - Restrict source IPs at the cloud firewall when work patterns allow
- Audit
authorized_keysquarterly - Keep a break-glass console procedure
- Test config after reboots, not only after
reload
Common Mistakes
- Disabling passwords before confirming key auth in a second session
AllowUserstypo that excludes you- Reloading despite
sshd -tfailure - Hardening sshd while UFW still allows the world and passwords remain on (incomplete story)
- Copying Windows line endings into config files
- Forgetting Cloud Firewall when changing ports
- Shared “team” SSH keys that nobody can rotate
- Disabling SSH entirely after WireGuard is up without a documented management path
FAQ
Is PasswordAuthentication no enough?
It is the most important single setting after installing keys, but combine it with PermitRootLogin no, firewall policy, and key inventory. Defense in depth still matters.
Should I use SSH certificates?
Certificates scale well for larger teams (short-lived user certs signed by an internal CA). For a single operator VPS, plain ed25519 keys are fine. Adopt certificates when key sprawl becomes the problem.
What about 2FA for SSH?
PAM-based TOTP works but increases lockout risk. Hardware-backed keys (FIDO2/sk-ssh-ed25519) are an excellent second factor for admin access when your OpenSSH and clients support them.
Why does reload work but reboot breaks SSH?
A validation path you skipped, a cloud-init rewrite, or a conflicting drop-in. Always sshd -t and test a new connection; reboot a staging host after hardening when you can.
Does WireGuard replace the need to harden SSH?
No. You still need SSH (or serial/console) for host management unless you have another controlled channel. Many teams later restrict SSH to WireGuard tunnel IPs—do that only after the tunnel is reliable.
How do I see the effective config?
sudo sshd -T
This prints the merged runtime settings—use it when drop-ins overlap.
Summary
SSH hardening on Ubuntu is a disciplined sshd_config (or drop-in): key-only auth, no root login, explicit AllowUsers, tightened session/auth limits, and disabled unused forwarding features. Validate with sshd -t, reload safely, and complete the host checklist—firewall, Fail2Ban, key inventory, and console recovery.
Treat SSH as production infrastructure. The VPN does not excuse a weak management plane.
If you want to automate VPN server deployment instead of configuring everything manually, TunnelFleet helps you provision and manage VPN infrastructure on your own cloud provider with minimal manual setup.
Share this article
Practical guides on VPN infrastructure, server automation, and self-hosted networking from the TunnelFleet team.
View all articles by TunnelFleet Editorial →