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.