SSH Hardening Guide

SSH Hardening Guide

SSH is port 22 on the open internet and gets probed by automated bots within minutes of a server going live. Most of these attacks are opportunistic — they try common passwords, default credentials, and weak keys. A properly hardened SSH configuration stops all of them cold.

The baseline sshd_config

All SSH server settings live in /etc/ssh/sshd_config. After every change, test the config and reload:

# Test sshd config for syntax errors (do this before every reload)
sudo sshd -t

# Reload sshd (applies changes without dropping existing sessions)
sudo systemctl reload sshd

# Check the active configuration (shows compiled-in defaults too)
sudo sshd -T

Disable root login

# /etc/ssh/sshd_config
PermitRootLogin no

There is no reason for root to log in directly over SSH. Use a regular user with sudo access. This eliminates the root account as an SSH target entirely.

If you absolutely need root-level access immediately on login (rare), use:

PermitRootLogin prohibit-password   # root can connect but only with a key, not password

Require key-based authentication

# /etc/ssh/sshd_config

# Disable all password-based authentication methods
PasswordAuthentication no
ChallengeResponseAuthentication no  # OpenSSH < 9.0
KbdInteractiveAuthentication no     # OpenSSH >= 9.0 (same thing, renamed)

# Ensure public key auth is enabled (it is by default, but be explicit)
PubkeyAuthentication yes

# Where to look for authorized keys
AuthorizedKeysFile .ssh/authorized_keys

Do this before disabling passwords: make sure your public key is in ~/.ssh/authorized_keys on the server and that you can connect with it in a separate terminal.

# Add your public key to the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub colton@yourserver

# Test key-based login in a NEW terminal (keep your current session open)
ssh -i ~/.ssh/id_ed25519 colton@yourserver

# Only disable password auth after confirming the key works

Restrict which users can connect

# /etc/ssh/sshd_config

# Whitelist specific users (only these users may connect via SSH)
AllowUsers colton deploy alice

# Or whitelist by group
AllowGroups sshusers admins

# If both are set, a user must satisfy both conditions
# DenyUsers and DenyGroups also exist but AllowUsers/AllowGroups is cleaner

Limit authentication attempts

# /etc/ssh/sshd_config

# Reduce the number of auth attempts per connection
MaxAuthTries 3

# How long to allow for the auth handshake before disconnecting
LoginGraceTime 20

# Maximum concurrent unauthenticated connections
MaxStartups 5:50:10
# Format: start:rate:full
# Allow 5 unauthenticated connections without penalty,
# then randomly drop 50% of new attempts until 10 are pending

Turn off unused features

# /etc/ssh/sshd_config

# Disable X11 forwarding (GUI forwarding, rarely needed on servers)
X11Forwarding no

# Disable TCP forwarding (port tunneling) if you do not need it
AllowTcpForwarding no

# Disable agent forwarding if not needed
AllowAgentForwarding no

# Disable tunnel device forwarding
PermitTunnel no

# Disable empty passwords (should already be the default)
PermitEmptyPasswords no

# Disable .rhosts and /etc/hosts.equiv authentication (legacy, insecure)
IgnoreRhosts yes
HostbasedAuthentication no

# Print last login time and motd on connect
PrintLastLog yes

Use a non-standard port (optional)

# /etc/ssh/sshd_config

# Change from default port 22
Port 2222

# Update your firewall before reloading sshd
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp
sudo systemctl reload sshd

Update your ~/.ssh/config on client machines:

Host myserver
    HostName yourserver.example.com
    Port 2222
    User colton
    IdentityFile ~/.ssh/id_ed25519

Cryptographic settings

Restrict SSH to strong, modern algorithms. Older algorithms (MD5, SHA1, diffie-hellman-group1-sha1) should be disabled.

# /etc/ssh/sshd_config

# Key exchange: prefer elliptic curve
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512

# Symmetric ciphers: prefer ChaCha20 and AES-GCM
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr

# Message authentication codes
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com

# Use only Ed25519 and RSA host keys (remove ECDSA and DSA)
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

Check your configuration with ssh-audit:

# Install ssh-audit
pip install ssh-audit --break-system-packages

# Or get it from your package manager
sudo apt install ssh-audit

# Audit your server
ssh-audit localhost
ssh-audit yourserver.example.com    # from another machine

Add idle session timeouts

# /etc/ssh/sshd_config

# Server sends a keepalive every 300 seconds
ClientAliveInterval 300

# Disconnect after 2 missed keepalives (10 minutes total idle)
ClientAliveCountMax 2

Harden host key management

# Regenerate host keys (do this on a new server, before any users connect)
# This removes old weak keys (ECDSA, DSA) and creates fresh strong ones

sudo rm -f /etc/ssh/ssh_host_*key*
sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
sudo ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
sudo systemctl restart sshd

# Show host key fingerprints (verify out-of-band with users connecting for the first time)
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
ssh-keygen -lf /etc/ssh/ssh_host_rsa_key.pub

Audit authorized_keys

# Check all authorized_keys files on the system
sudo find /home -name authorized_keys -exec echo "=== {} ===" \; -exec cat {} \;
sudo cat /root/.ssh/authorized_keys 2>/dev/null

# Check when authorized_keys files were last modified
find /home -name authorized_keys -newer /tmp/ref -exec ls -la {} \;
# (create /tmp/ref with touch -d '30 days ago' /tmp/ref first)

# Verify each key: get the fingerprint of every authorized key
while IFS= read -r key; do
    echo "$key" | ssh-keygen -lf -
done < /home/colton/.ssh/authorized_keys

A complete hardened sshd_config

# /etc/ssh/sshd_config -- hardened configuration

Port 22                            # change to non-standard if desired
AddressFamily inet                 # IPv4 only; use 'any' for both

# Authentication
PermitRootLogin no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
AllowUsers colton

# Session limits
MaxAuthTries 3
LoginGraceTime 20
MaxStartups 5:50:10
MaxSessions 5

# Timeouts
ClientAliveInterval 300
ClientAliveCountMax 2

# Disable unused features
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
IgnoreRhosts yes
HostbasedAuthentication no

# Host keys
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

# Logging
SyslogFacility AUTH
LogLevel VERBOSE

# Other
PrintLastLog yes
PrintMotd no
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
# Validate and apply
sudo sshd -t && sudo systemctl reload sshd

Monitor SSH attempts

# Watch live SSH auth attempts
sudo journalctl -u sshd -f

# Count failed attempts by IP
sudo journalctl -u sshd --since "24 hours ago" | \
  grep "Failed password\|Invalid user" | \
  awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20

# Check for successful logins
sudo journalctl -u sshd | grep "Accepted"

# View all auth events
sudo grep -E "sshd.*Failed|sshd.*Accepted|sshd.*Invalid" /var/log/auth.log

Frequently Asked Questions

What are the most important SSH hardening steps?

The highest-impact SSH hardening steps are: disable password authentication and require key-based auth (PasswordAuthentication no); disable root login (PermitRootLogin no); restrict which users can connect (AllowUsers colton); install fail2ban or sshguard to automatically ban IPs with repeated failed attempts; keep the OpenSSH server package updated; and optionally change the default port from 22 to reduce automated scanning noise (this is security-by-obscurity but does reduce log spam). Together these steps stop the vast majority of automated SSH attacks.

How do I disable SSH password authentication?

Edit /etc/ssh/sshd_config and set PasswordAuthentication no and ChallengeResponseAuthentication no (or KbdInteractiveAuthentication no on newer OpenSSH). Before doing this, make sure your SSH key is in ~/.ssh/authorized_keys on the server and that you can log in with the key. Test from a second terminal before closing your current session. Then reload the daemon: sudo systemctl reload sshd. If you lock yourself out, most cloud providers offer an out-of-band console to fix the configuration.

Should I change the SSH port from 22?

Changing the SSH port (Port 2222 or any non-standard port in /etc/ssh/sshd_config) reduces automated scanning and log noise because the vast majority of bots target port 22. It is not a security control in itself — any attacker who specifically targets your server will find the port with a full port scan. However, it does eliminate low-effort opportunistic attacks and makes your auth logs much less noisy, which makes it easier to spot genuine threats. If you change the port, update your firewall rules and ~/.ssh/config on your client machines.

What is an SSH authorized_keys file?

The ~/.ssh/authorized_keys file on the server contains the public keys of clients allowed to connect. Each line is one public key in OpenSSH format. When a client tries to authenticate, the server checks whether the client can prove it holds the private key corresponding to any public key in authorized_keys. If it can, authentication succeeds without a password. The file must be owned by the user (not writable by others) and the ~/.ssh directory must have permissions 700, otherwise OpenSSH ignores it as a security precaution.

What are SSH host keys and why does “WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED” appear?

SSH host keys are key pairs generated on the server that prove the server’s identity to clients. When you first connect to a server, SSH records the server’s public host key in ~/.ssh/known_hosts. On subsequent connections, SSH verifies that the server presents the same key. If the key changes — because the server was rebuilt, the IP was reassigned to a different machine, or the keys were regenerated — SSH warns you that the identity has changed. This warning exists to protect against man-in-the-middle attacks. If the change is expected (server rebuild), remove the old entry with ssh-keygen -R hostname and reconnect.

How do I audit which SSH keys are authorized on a server?

Check ~/.ssh/authorized_keys for each user: cat /home/*/.ssh/authorized_keys and sudo cat /root/.ssh/authorized_keys. Each line is one authorized public key. Compare against keys you actually issued and remove any you do not recognise. Also check /etc/ssh/authorized_keys if AuthorizedKeysFile is set to a global location. Tools like ssh-audit check the server’s cryptographic configuration (key exchange algorithms, ciphers, MACs) and flag weak settings. Run it with: ssh-audit localhost.