Security

SSH Hardening on Linux

ยท2 min read

SSH is the door to every server. Most attacks against Linux infrastructure are unsophisticated โ€” they're login brute-force, credential stuffing, exposed root accounts. A correctly configured SSH daemon defeats most of them with a config file change.

The minimum config

# /etc/ssh/sshd_config (or a drop-in in /etc/ssh/sshd_config.d/)

# Authentication
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
PubkeyAuthentication yes

# Session
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2

# Restrict who can log in at all
AllowUsers alice bob deploy
# Or by group:
# AllowGroups ssh-users

# Modern crypto
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

After editing: sshd -t to test, then systemctl reload ssh. Keep your current session open while you test.

Keys, not passwords

# Generate (ed25519 โ€” small, fast, modern)
ssh-keygen -t ed25519 -C "alice@laptop-2026"

# Copy to a server
ssh-copy-id alice@server

# Or paste manually
cat ~/.ssh/id_ed25519.pub | ssh server 'cat >> ~/.ssh/authorized_keys'
ssh server 'chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys'

Port choice

Moving SSH off port 22 is security-through-obscurity, but it does reduce log noise from random scans dramatically. Pick a high port and document it in your ~/.ssh/config. The real protection is still pubkey-only auth.

fail2ban

For the inevitable brute-force noise. fail2ban watches the auth log and adds firewall rules to ban offending IPs after N failures.

# /etc/fail2ban/jail.local
[sshd]
enabled  = true
port     = ssh
filter   = sshd
backend  = systemd
maxretry = 3
findtime = 10m
bantime  = 1h

Per-key restrictions

An entry in authorized_keys can prefix the key with restrictions. Useful for deploy keys and rsync-only accounts:

from="10.0.0.0/8",no-pty,no-agent-forwarding,no-X11-forwarding,command="/usr/local/bin/rsync-only" ssh-ed25519 AAAA... deploy@ci

Audit

journalctl -u ssh -f                # live auth attempts
last -F                              # successful logins
lastb                                # failed logins (root only)
ss -tnp state established '( dport = :ssh or sport = :ssh )'   # current sessions

2FA

For human accounts on production boxes, add a second factor. Google Authenticator's PAM module is the lowest-friction option โ€” see TOTP on Linux.

Related