SSH Hardening and Key Management: The Complete Security Guide
Secure your SSH infrastructure with modern hardening techniques. Covers key types, agent forwarding, certificate-based auth, jump hosts, and automated key...
SSH Is Your Front Door
SSH is the most common entry point into servers. A poorly configured SSH setup is an open invitation for attackers. Credential stuffing, brute force, and stolen keys account for over 60% of server compromises. Let us fix that.
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 220" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="220" rx="12" fill="#1a1a2e"/><path d="M300,25 L380,55 L380,120 Q380,170 300,195 Q220,170 220,120 L220,55 Z" fill="none" stroke="#6366f1" stroke-width="2.5"/><path d="M300,40 L365,65 L365,118 Q365,160 300,180 Q235,160 235,118 L235,65 Z" fill="#6366f1" opacity="0.15"/><rect x="280" y="95" width="40" height="30" rx="4" fill="#6366f1" opacity="0.9"/><path d="M288,95 L288,82 Q288,72 300,72 Q312,72 312,82 L312,95" fill="none" stroke="#6366f1" stroke-width="2.5"/><circle cx="300" cy="110" r="4" fill="#ffffff"/><text x="90" y="60" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="system-ui">Firewall</text><line x1="130" y1="57" x2="218" y2="57" stroke="#3b82f6" stroke-width="1" stroke-dasharray="3,3"/><text x="90" y="100" text-anchor="middle" fill="#a855f7" font-size="10" font-family="system-ui">WAF</text><line x1="110" y1="97" x2="220" y2="85" stroke="#a855f7" stroke-width="1" stroke-dasharray="3,3"/><text x="90" y="140" text-anchor="middle" fill="#2dd4bf" font-size="10" font-family="system-ui">SSO / MFA</text><line x1="130" y1="137" x2="222" y2="120" stroke="#2dd4bf" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="60" text-anchor="middle" fill="#f59e0b" font-size="10" font-family="system-ui">TLS/SSL</text><line x1="470" y1="57" x2="382" y2="57" stroke="#f59e0b" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="100" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="system-ui">RBAC</text><line x1="490" y1="97" x2="380" y2="85" stroke="#3b82f6" stroke-width="1" stroke-dasharray="3,3"/><text x="510" y="140" text-anchor="middle" fill="#a855f7" font-size="10" font-family="system-ui">Audit Logs</text><line x1="470" y1="137" x2="378" y2="120" stroke="#a855f7" stroke-width="1" stroke-dasharray="3,3"/></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Defense in depth: multiple security layers protect your infrastructure from threats.</p></div>
Key Generation — Use Ed25519
RSA keys are still common, but Ed25519 is superior in every way:
# Generate an Ed25519 key (recommended)
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519_prod
# If you need RSA compatibility (legacy systems), use 4096 bits minimum
ssh-keygen -t rsa -b 4096 -C "[email protected]" -f ~/.ssh/id_rsa_legacyWhy Ed25519:
SSH Server Hardening
Edit /etc/ssh/sshd_config with these security settings:
# /etc/ssh/sshd_config — Hardened Configuration
# Disable password authentication entirely
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
PubkeyAuthentication yes
# Disable root login (use sudo instead)
PermitRootLogin no
# Restrict to specific users/groups
AllowGroups ssh-users
# Use only protocol 2
Protocol 2
# Strong key exchange algorithms
KexAlgorithms [email protected],curve25519-sha256,[email protected]
Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected]
# Disable unused features
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
# Rate limiting
MaxAuthTries 3
MaxSessions 5
LoginGraceTime 30
# Idle timeout (disconnect after 10 min idle)
ClientAliveInterval 300
ClientAliveCountMax 2
# Logging
LogLevel VERBOSE
SyslogFacility AUTHAfter editing, validate and restart:
# Validate config before restarting (prevents lockout!)
sshd -t
# Restart SSH
sudo systemctl restart sshdSSH Config for Clients
Organize your connections with ~/.ssh/config:
# ~/.ssh/config
# Default settings for all hosts
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
IdentitiesOnly yes
# Production server via jump host
Host prod
HostName 10.0.1.50
User deploy
IdentityFile ~/.ssh/id_ed25519_prod
ProxyJump jump.example.com
# Jump/Bastion host
Host jump.example.com
User jumpuser
IdentityFile ~/.ssh/id_ed25519_jump
ForwardAgent no
# Development server
Host dev
HostName dev.example.com
User developer
IdentityFile ~/.ssh/id_ed25519_dev
LocalForward 5432 localhost:5432
LocalForward 6379 localhost:6379SSH Certificate Authority
For teams larger than 3 people, SSH certificates are far superior to distributing public keys:
# Create a CA key pair (do this once, guard the private key)
ssh-keygen -t ed25519 -f /etc/ssh/ca_key -C "SSH CA"
# Sign a user's public key (valid 8 hours, for user "deploy")
ssh-keygen -s /etc/ssh/ca_key \
-I "deploy@company" \
-n deploy \
-V +8h \
~/.ssh/id_ed25519.pub
# Sign a host key (valid 1 year)
ssh-keygen -s /etc/ssh/ca_key \
-I "web1.example.com" \
-h \
-n web1.example.com \
-V +52w \
/etc/ssh/ssh_host_ed25519_key.pubConfigure the server to trust the CA:
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca_key.pubConfigure clients to trust host certificates:
# ~/.ssh/known_hosts
@cert-authority *.example.com ssh-ed25519 AAAA...Now you never need to manage authorized_keys files or deal with host key verification warnings again.
<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 150" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="150" rx="12" fill="#1a1a2e"/><rect x="30" y="40" width="100" height="55" rx="6" fill="none" stroke="#3b82f6" stroke-width="1.5"/><text x="80" y="60" text-anchor="middle" fill="#3b82f6" font-size="10" font-family="monospace">Hello World</text><text x="80" y="80" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Plaintext</text><rect x="175" y="30" width="90" height="75" rx="8" fill="#6366f1" opacity="0.85"/><text x="220" y="55" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Encrypt</text><text x="220" y="72" text-anchor="middle" fill="#ffffff" font-size="9" font-family="system-ui">AES-256</text><text x="220" y="92" text-anchor="middle" fill="#f59e0b" font-size="20" font-family="system-ui">🔑</text><rect x="310" y="40" width="100" height="55" rx="6" fill="none" stroke="#a855f7" stroke-width="1.5"/><text x="360" y="60" text-anchor="middle" fill="#a855f7" font-size="10" font-family="monospace">x8f2...k9z</text><text x="360" y="80" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Ciphertext</text><rect x="455" y="30" width="90" height="75" rx="8" fill="#2dd4bf" opacity="0.85"/><text x="500" y="55" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Decrypt</text><text x="500" y="72" text-anchor="middle" fill="#1a1a2e" font-size="9" font-family="system-ui">AES-256</text><text x="500" y="92" text-anchor="middle" fill="#f59e0b" font-size="20" font-family="system-ui">🔑</text><defs><marker id="arrow6" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="132" y1="67" x2="173" y2="67" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow6)"/><line x1="267" y1="67" x2="308" y2="67" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow6)"/><line x1="412" y1="67" x2="453" y2="67" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow6)"/><text x="300" y="130" text-anchor="middle" fill="#94a3b8" font-size="10" font-family="system-ui">Symmetric Encryption: same key encrypts and decrypts</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Encryption transforms readable plaintext into unreadable ciphertext, reversible only with the correct key.</p></div>
Fail2Ban Configuration
# /etc/fail2ban/jail.d/sshd.conf
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
ignoreip = 192.168.1.0/24Automated Key Rotation
Create a script to rotate SSH keys quarterly:
#!/bin/bash
# rotate-ssh-keys.sh
KEY_DIR="\$HOME/.ssh"
OLD_KEY="\$KEY_DIR/id_ed25519_prod"
NEW_KEY="\$KEY_DIR/id_ed25519_prod_new"
SERVERS="web1 web2 db1"
# Generate new key
ssh-keygen -t ed25519 -f "\$NEW_KEY" -N "" -C "deploy@company-\$(date +%Y%m)"
# Deploy new key to all servers
for server in \$SERVERS; do
ssh-copy-id -i "\$NEW_KEY.pub" "\$server"
echo "Deployed to \$server"
done
# Test new key works
for server in \$SERVERS; do
ssh -i "\$NEW_KEY" "\$server" "echo 'New key works'" || {
echo "FAILED on \$server — aborting rotation"
exit 1
}
done
# Remove old key from servers
for server in \$SERVERS; do
OLD_PUB=\$(cat "\$OLD_KEY.pub")
ssh -i "\$NEW_KEY" "\$server" "sed -i '\\|\$OLD_PUB|d' ~/.ssh/authorized_keys"
done
# Replace old key locally
mv "\$OLD_KEY" "\$OLD_KEY.bak.\$(date +%Y%m%d)"
mv "\$OLD_KEY.pub" "\$OLD_KEY.pub.bak.\$(date +%Y%m%d)"
mv "\$NEW_KEY" "\$OLD_KEY"
mv "\$NEW_KEY.pub" "\$OLD_KEY.pub"
echo "Key rotation complete"Port Knocking (Defense in Depth)
Add port knocking as an additional layer:
# Install knockd
sudo apt install knockd
# /etc/knockd.conf
[options]
UseSyslog
[openSSH]
sequence = 7000,8000,9000
seq_timeout = 5
command = ufw allow from %IP% to any port 22
tcpflags = syn
[closeSSH]
sequence = 9000,8000,7000
seq_timeout = 5
command = ufw delete allow from %IP% to any port 22
tcpflags = synAudit Logging
Track who logged in, when, and what they did:
# Enable session recording with script command
# Add to /etc/profile
if [ -n "\$SSH_CONNECTION" ]; then
LOGDIR="/var/log/ssh-sessions"
mkdir -p "\$LOGDIR"
LOGFILE="\$LOGDIR/\$(whoami)_\$(date +%Y%m%d_%H%M%S)_\$\$.log"
script -qf "\$LOGFILE"
fi<div style="margin:2.5rem auto;max-width:600px;width:100%;text-align:center;"><svg viewBox="0 0 600 180" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;"><rect width="600" height="180" rx="12" fill="#1a1a2e"/><circle cx="60" cy="90" r="20" fill="none" stroke="#3b82f6" stroke-width="2"/><text x="60" y="94" text-anchor="middle" fill="#3b82f6" font-size="11" font-family="system-ui">User</text><rect x="120" y="65" width="95" height="50" rx="8" fill="#6366f1" opacity="0.85"/><text x="167" y="85" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Identity</text><text x="167" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Verify</text><rect x="250" y="65" width="95" height="50" rx="8" fill="#a855f7" opacity="0.85"/><text x="297" y="85" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Policy</text><text x="297" y="100" text-anchor="middle" fill="#ffffff" font-size="10" font-family="system-ui">Engine</text><rect x="380" y="65" width="95" height="50" rx="8" fill="#2dd4bf" opacity="0.85"/><text x="427" y="85" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Access</text><text x="427" y="100" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">Proxy</text><rect x="510" y="65" width="60" height="50" rx="8" fill="#f59e0b" opacity="0.85"/><text x="540" y="94" text-anchor="middle" fill="#1a1a2e" font-size="10" font-family="system-ui">App</text><defs><marker id="arrow5" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6" fill="#e2e8f0"/></marker></defs><line x1="82" y1="90" x2="118" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="217" y1="90" x2="248" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="347" y1="90" x2="378" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><line x1="477" y1="90" x2="508" y2="90" stroke="#e2e8f0" stroke-width="1.5" marker-end="url(#arrow5)"/><text x="167" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">MFA + Device</text><text x="297" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Least Privilege</text><text x="427" y="140" text-anchor="middle" fill="#94a3b8" font-size="9" font-family="system-ui">Encrypted Tunnel</text><text x="300" y="165" text-anchor="middle" fill="#6366f1" font-size="11" font-family="system-ui" font-weight="bold">Never Trust, Always Verify</text></svg><p style="margin-top:0.75rem;font-size:0.85rem;color:#94a3b8;font-style:italic;line-height:1.4;">Zero Trust architecture: every request is verified through identity, policy, and access proxy layers.</p></div>
Security Checklist
At TechSaaS, we follow all of these practices. Our servers are accessible only via Ed25519 keys through a jump host, with CrowdSec providing real-time intrusion prevention. Security is not a feature — it is the foundation.
Need an SSH security audit? Reach out at [email protected].
Need help with security?
TechSaaS provides expert consulting and managed services for cloud infrastructure, DevOps, and AI/ML operations.