TunnelFleet
SSH Hardening Best Practices
Security 9 min read 1 views

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:

  • MaxAuthTries caps attempts per connection
  • LoginGraceTime drops slow authenticating connections
  • MaxStartups throttles unauthenticated concurrent connections (useful under scan floods)
  • Exact PerSourceMaxStartups support depends on OpenSSH version—sshd -T shows 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_keys ownership and modes correct
  • UFW allows SSH (optionally from admin IPs only)
  • DigitalOcean Cloud Firewall mirrors SSH policy
  • Fail2Ban sshd jail enabled with ignoreip for your admin IP
  • Unattended security upgrades enabled
  • ss -tulpn shows 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:

  1. WireGuard is enabled at boot and reliably returns
  2. You have DigitalOcean console access tested
  3. 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 addresses
  • command= — 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_keys lines 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 -t every 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_keys quarterly
  • 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
  • AllowUsers typo that excludes you
  • Reloading despite sshd -t failure
  • 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.

Tags: Ubuntu Security VPS DevOps Hardening SSH OpenSSH sshd_config

Share this article

T

Practical guides on VPN infrastructure, server automation, and self-hosted networking from the TunnelFleet team.

View all articles by TunnelFleet Editorial →

Related Articles