SSH is the front door to your infrastructure. Every server, every cloud instance, every piece of automation relies on it. A compromised SSH setup means game over.
These hardening steps dramatically reduce your attack surface.
Disable Password Authentication# Passwords can be brute-forced. Keys can’t (practically):
1
2
3
4
# /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
1
2
# Restart SSH (keep your current session open!)
sudo systemctl restart sshd
Before doing this, ensure your key is in ~/.ssh/authorized_keys.
Use Strong Key Types# 1
2
3
4
5
# Generate Ed25519 key (recommended)
ssh-keygen -t ed25519 -C "your_email@example.com"
# Or RSA with 4096 bits minimum
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
Ed25519 is faster, smaller, and considered more secure than RSA.
Disable Root Login# Never allow direct root SSH:
1
2
# /etc/ssh/sshd_config
PermitRootLogin no
Use a regular user and sudo instead. This adds accountability and a second authentication layer.
Limit User Access# Only allow specific users to SSH:
1
2
3
4
# /etc/ssh/sshd_config
AllowUsers alice bob deploy
# Or by group
AllowGroups sshusers admins
Everyone else is rejected before authentication even starts.
Change the Default Port# Security through obscurity isn’t security, but it reduces noise:
1
2
# /etc/ssh/sshd_config
Port 2222
1
2
3
# Don't forget to update firewall
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp
This eliminates 99% of automated scanning bots.
Rate Limiting with Fail2ban# Block IPs after failed attempts:
1
sudo apt install fail2ban
1
2
3
4
5
6
7
8
9
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
Three failures in 10 minutes = banned for an hour.
SSH Config Hardening# Full hardened configuration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# /etc/ssh/sshd_config
# Protocol and port
Port 2222
Protocol 2
AddressFamily inet # IPv4 only, or inet6 for IPv6
# Authentication
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# Limit users
AllowUsers deploy admin
# Timeouts and limits
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 2
# Disable unused features
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
# Logging
SyslogFacility AUTH
LogLevel VERBOSE
# Cryptography (modern algorithms only)
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
Test before applying:
1
sudo sshd -t # Syntax check
Key-Based Access Control# Restrict what keys can do:
1
2
3
4
5
6
# ~/.ssh/authorized_keys
# Limit to specific commands
command = "/usr/local/bin/backup.sh" ,no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAA... backup-key
# Restrict source IP
from = "192.168.1.0/24" ,no-port-forwarding ssh-ed25519 AAAA... office-key
Two-Factor Authentication# Add TOTP to SSH:
1
2
sudo apt install libpam-google-authenticator
google-authenticator # Run as user, follow prompts
1
2
3
4
5
6
# /etc/pam.d/sshd
auth required pam_google_authenticator.so
# /etc/ssh/sshd_config
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
Now login requires: valid key + TOTP code.
SSH Certificates (Advanced)# For larger teams, use certificates instead of distributing keys:
1
2
3
4
5
6
7
8
# Create CA
ssh-keygen -t ed25519 -f ca_key -C "SSH CA"
# Sign a user key
ssh-keygen -s ca_key -I user_alice -n alice -V +52w alice_key.pub
# Server trusts CA
echo "TrustedUserCAKeys /etc/ssh/ca_key.pub" >> /etc/ssh/sshd_config
Revoke access by revoking certificates, not hunting down authorized_keys files.
Bastion Host Pattern# Don’t expose all servers to the internet:
I n t e r n e t → B a s t i o n ( h a r d e n e d ) → I n t e r n a l s e r v e r s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ~/.ssh/config on your laptop
Host bastion
HostName bastion.example.com
User admin
Port 2222
IdentityFile ~/.ssh/bastion_key
Host internal-*
ProxyJump bastion
User deploy
IdentityFile ~/.ssh/internal_key
Host internal-web
HostName 10.0.1.10
Host internal-db
HostName 10.0.1.20
1
2
# Connect through bastion automatically
ssh internal-web
Audit SSH Access# Know who’s logging in:
1
2
3
4
5
6
7
8
# Recent logins
last -a | head -20
# Failed attempts
grep "Failed password" /var/log/auth.log | tail -20
# Accepted keys
grep "Accepted publickey" /var/log/auth.log | tail -20
Set up alerts for:
Successful logins from new IPs Multiple failed attempts Root login attempts (should be zero) SSH Agent Forwarding Risks# Agent forwarding is convenient but dangerous:
1
2
3
4
5
# Dangerous: forwards your agent to remote host
ssh -A server
# Anyone with root on that server can use your agent
# to authenticate as you to other systems
Use ProxyJump instead of agent forwarding when possible.
Quick Security Checklist# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Verify your config
sudo sshd -T | grep -E "passwordauth|permitroot|port|allowusers"
# Check for weak keys
for key in /etc/ssh/ssh_host_*_key.pub; do
ssh-keygen -l -f " $key "
done
# Review authorized keys
cat ~/.ssh/authorized_keys
# Check who's connected now
who
ss -tn | grep :22
SSH hardening is layers: strong keys, no passwords, limited users, rate limiting, monitoring. No single measure is enough; together they make compromise significantly harder.
Test changes carefully — locking yourself out of a remote server is a bad day. Always keep a session open while testing SSH config changes.