Skip to content

Ssh_hardening

Chapter 32: SSH Hardening and Key Management - Deep Dive

Section titled “Chapter 32: SSH Hardening and Key Management - Deep Dive”

Mastering SSH Security for Production Environments

Section titled “Mastering SSH Security for Production Environments”

SSH Protocol Layers
+------------------------------------------------------------------+
| |
| SSH Application Layer |
| +-------------------------------------------------------------+ |
| | sftp, scp, ssh-add, ssh-keygen, ssh-agent | |
| +-------------------------------------------------------------+ |
| | |
| v |
| SSH Connection Protocol |
| +-------------------------------------------------------------+ |
| | - Channel management | |
| | - Session multiplexing | |
| | - X11 forwarding | |
| | - Agent forwarding | |
| | - TCP forwarding | |
| +-------------------------------------------------------------+ |
| | |
| v |
| SSH Authentication Protocol |
| +-------------------------------------------------------------+ |
| | - publickey (RSA, ECDSA, Ed25519) | |
| | - password | |
| | - keyboard-interactive | |
| | - hostbased | |
| +-------------------------------------------------------------+ |
| | |
| v |
| SSH Transport Layer Protocol |
| +-------------------------------------------------------------+ |
| | - Server authentication | |
| | - Key exchange (DH, ECDH) | |
| | - Encryption (AES, ChaCha20) | |
| | - MAC (HMAC, Poly1305) | |
| | - Compression (zlib) | |
| +-------------------------------------------------------------+ |
| | |
| v |
| TCP/IP Layer |
| |
+------------------------------------------------------------------+
SSH Protocol Comparison
+------------------------------------------------------------------+
| |
| SSH Version 1 |
| +----------------------------------------------------------+ |
| | - Deprecated due to security vulnerabilities | |
| | - Single encryption tunnel | |
| | - Vulnerable to man-in-the-middle attacks | |
| | - No integrity checking for channels | |
| | - Still exists on some legacy systems | |
| +----------------------------------------------------------+ |
| |
| SSH Version 2 |
| +----------------------------------------------------------+ |
| | - Current standard (RFC 4251-4256) | |
| | - Multiple encryption channels | |
| | - Strong key exchange algorithms | |
| | - Better integrity checking (MAC) | |
| | - Modular design | |
| | - Extension support | |
| +----------------------------------------------------------+ |
| |
| Key Differences: |
| +----------------------------------------------------------+ |
| | Feature | SSHv1 | SSHv2 | |
| | ---------------|------------|----------------------------| |
| | Encryption | Single | Multiple channels | |
| | Key Exchange | Fixed | Modular (diffie-hellman) | |
| | Integrity | Weak | HMAC | |
| | Security | Weak | Strong | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+

Terminal window
# /etc/ssh/sshd_config - Production Hardened Configuration
# =============================================================================
# SSH PROTOCOL SETTINGS
# =============================================================================
# Use only SSH Protocol 2 (mandatory for security)
Protocol 2
# Listen on specific interfaces (reduce attack surface)
ListenAddress 0.0.0.0
ListenAddress ::
# =============================================================================
# AUTHENTICATION SETTINGS
# =============================================================================
# Disable root login (critical security measure)
PermitRootLogin no
# Disable empty passwords
PermitEmptyPasswords no
# Disable password authentication (use keys only)
PasswordAuthentication no
PermitUserEnvironment no
ChallengeResponseAuthentication no
# Maximum authentication attempts
MaxAuthTries 3
MaxSessions 10
# Login grace time
LoginGraceTime 30
# Strict modes - check file permissions
StrictModes yes
# Allow specific users/groups (whitelist approach)
AllowUsers admin deployuser automation
# AllowGroups sudo developers
# =============================================================================
# KEY/BANNER SETTINGS
# =============================================================================
# Login banner
Banner /etc/ssh/banner
# Public key authentication
PubkeyAuthentication yes
# AuthorizedKeysFile location
AuthorizedKeysFile .ssh/authorized_keys
# =============================================================================
# SESSION SETTINGS
# =============================================================================
# Client alive settings (detect disconnected clients)
ClientAliveInterval 300
ClientAliveCountMax 2
# Session timeout
# Idle timeout (set in client)
# Add to client config: ServerAliveInterval 300
# =============================================================================
# FORWARDING SETTINGS
# =============================================================================
# Disable all forwarding (if not needed)
# Disable X11 forwarding (security risk)
X11Forwarding no
# Disable agent forwarding
AllowAgentForwarding no
# Disable TCP forwarding
AllowTcpForwarding no
# Disable tunnel forwarding
# PermitTunnel no
# =============================================================================
# ENCRYPTION AND HASHING
# =============================================================================
# Ciphers (order matters - put strongest first)
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
# MAC (Message Authentication Codes)
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
# Key exchange algorithms
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
# =============================================================================
# HOSTKEY SETTINGS
# =============================================================================
# HostKeys for protocol 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
# Use privileged port (security)
# HostKey /etc/ssh/ssh_host_key (SSHv1 - disable)
# =============================================================================
# SUBSYSTEMS
# =============================================================================
Subsystem sftp /usr/lib/ssh/sftp-server -f AUTHPRIV -l INFO
# =============================================================================
# ACCESS CONTROL
# =============================================================================
# Deny certain users
# DenyUsers baduser
# Kerberos options
KerberosAuthentication no
KerberosOrLocalPasswd yes
KerberosTicketCleanup yes
# GSSAPI options
GSSAPIAuthentication no
GSSAPICleanupCredentials yes
# =============================================================================
# LOGGING
# =============================================================================
# Log level
LogLevel VERBOSE
# =============================================================================
# ENVIRONMENT
# =============================================================================
# Accept environment variables from client (disable for security)
AcceptEnv LANG LC_*
# =============================================================================
# PAM SETTINGS
# =============================================================================
UsePAM yes
# =============================================================================
# OTHER SETTINGS
# =============================================================================
# Print last login info
PrintLastLog yes
# Print MOTD
PrintMotd no
# Handle deprecated options
# IgnoreRhosts yes
# RhostsRSAAuthentication no
# HostbasedAuthentication no
# Allow shared connections
# ControlMaster auto
# ControlPath /tmp/ssh_mux_%h_%p
# ControlPersist 4h
/etc/ssh/banner
+------------------------------------------------------------------+
| |
| AUTHORIZED ACCESS ONLY |
| |
| If you are not authorized to access or use this system, |
| disconnect immediately. |
| |
| All connections are logged and monitored. |
| Unauthorized access is prohibited and will be prosecuted. |
| |
| By using this system, you consent to these terms. |
| |
+------------------------------------------------------------------+

SSH Key Types Comparison
+------------------------------------------------------------------+
| |
| RSA (Rivest-Shamir-Adleman) |
| +----------------------------------------------------------+ |
| | Key Sizes: 1024, 2048, 3072, 4096 bits | |
| | Compatibility: Excellent (all SSH clients) | |
| | Security: Good (2048+ bits recommended) | |
| | Performance: Slower than Ed25519 | |
| | Use Case: Legacy systems, compatibility | |
| +----------------------------------------------------------+ |
| |
| ECDSA (Elliptic Curve DSA) |
| +----------------------------------------------------------+ |
| | Key Sizes: 256, 384, 521 bits | |
| | Compatibility: Good (modern clients) | |
| | Security: Good (depends on curve) | |
| | Performance: Faster than RSA | |
| | Use Case: Balanced performance/security | |
| | Concerns: NIST curves (potential backdoor concerns) | |
| +----------------------------------------------------------+ |
| |
| Ed25519 (Edwards-curve DSA) |
| +----------------------------------------------------------+ |
| | Key Size: 256 bits (fixed) | |
| | Compatibility: Very Good (modern clients) | |
| | Security: Excellent (modern, well-reviewed) | |
| | Performance: Fastest | |
| | Key Size: Smallest (37 bytes public, 64 bytes priv) | |
| | Use Case: RECOMMENDED for new deployments | |
| +----------------------------------------------------------+ |
| |
| Ed448 (Edwards-curve DSA) |
| +----------------------------------------------------------+ |
| | Key Size: 448 bits (fixed) | |
| | Compatibility: Limited (newer) | |
| | Security: Highest (largest key) | |
| | Performance: Very fast | |
| | Use Case: Maximum security requirements | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
Terminal window
# =============================================================================
# ED25519 KEY (RECOMMENDED)
# =============================================================================
# Generate Ed25519 key with comment
ssh-keygen -t ed25519 -C "work@company.com - Production"
# Ed25519 with custom filename
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_production -C "production-server"
# Ed25519 with passphrase (REQUIRED for production)
ssh-keygen -t ed25519 -C "production" -a 100
# -a rounds: Key derivation function iterations (more = slower but secure)
# =============================================================================
# RSA KEY (FOR LEGACY COMPATIBILITY)
# =============================================================================
# RSA 4096-bit key
ssh-keygen -t rsa -b 4096 -C "legacy@company.com"
# RSA with more KDF rounds (slower but secure)
ssh-keygen -t rsa -b 4096 -a 100 -C "production"
# =============================================================================
# ED448 KEY (MAXIMUM SECURITY)
# =============================================================================
ssh-keygen -t ed448 -C "maximum-security"
# =============================================================================
# VIEW KEY DETAILS
# =============================================================================
# View public key
cat ~/.ssh/id_ed25519.pub
# View key fingerprint
ssh-keygen -lf ~/.ssh/id_ed25519.pub
# View key fingerprint (MD5 - useful for comparing)
ssh-keygen -lf ~/.ssh/id_ed25519.pub -E md5
# View key comments
ssh-keygen -y -f ~/.ssh/id_ed25519
# =============================================================================
# KEY MANAGEMENT
# =============================================================================
# Change passphrase
ssh-keygen -p -f ~/.ssh/id_ed25519
# Change comment
ssh-keygen -c -f ~/.ssh/id_ed25519 -C "new-comment"
# Generate public key from private key
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub
Terminal window
# =============================================================================
# METHOD 1: ssh-copy-id (Simplest)
# =============================================================================
# Standard copy
ssh-copy-id user@server
# Copy specific key
ssh-copy-id -i ~/.ssh/id_ed25519_production.pub user@server
# =============================================================================
# METHOD 2: Manual Copy
# =============================================================================
# Create .ssh directory and set permissions
ssh user@server "mkdir -p ~/.ssh && chmod 700 ~/.ssh"
# Copy public key
cat ~/.ssh/id_ed25519.pub | ssh user@server "cat >> ~/.ssh/authorized_keys"
# Set correct permissions
ssh user@server "chmod 600 ~/.ssh/authorized_keys && chmod 700 ~/.ssh"
# =============================================================================
# METHOD 3: Using Ansible
# =============================================================================
ansible all -m authorized_key -a "user=deploy key={{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
# =============================================================================
# METHOD 4: SSH Key Deployment (Restricted)
# =============================================================================
# Create restricted authorized_keys
# Force specific options per key
# Command restriction (user can only run specific commands)
command="/usr/local/bin/deploy.sh",no-pty,no-agent-forwarding,no-X11-forwarding ssh-rsa AAAA... user@host
# From specific IP only
from="192.168.1.100" ssh-rsa AAAA... user@host
# Multiple restrictions
from="192.168.1.0/24",command="/usr/bin/git-shell" ssh-rsa AAAA... git@host

SSH Agent Architecture
+------------------------------------------------------------------+
| |
| SSH Agent: In-memory key manager for SSH authentication |
| |
| +----------------------------------------------------------+ |
| | ssh-agent (runs as background process) | |
| | +------------------------------------------------------+ | |
| | | - Holds decrypted private keys in memory | | |
| | | - Communicates with SSH client via socket | | |
| | | - Keys never written to disk in decrypted form | | |
| | +------------------------------------------------------+ | |
| +----------------------------------------------------------+ |
| |
| Security Concerns: |
| +----------------------------------------------------------+ |
| | 1. Keys in memory can be extracted (cold boot attack) | |
| | 2. Agent forwarding exposes keys to remote servers | |
| | 3. Agent can be hijacked if socket is compromised | |
| | 4. Lock agent when leaving workstation | |
| +----------------------------------------------------------+ |
| |
+------------------------------------------------------------------+
Terminal window
# =============================================================================
# START SSH AGENT
# =============================================================================
# Start agent and set environment
eval "$(ssh-agent -s)"
# Or start agent in background
ssh-agent -s > /tmp/ssh-agent-env
source /tmp/ssh-agent-env
# Check if agent is running
echo $SSH_AUTH_SOCK
# =============================================================================
# ADD KEYS TO AGENT
# =============================================================================
# Add key with default settings
ssh-add
# Add specific key
ssh-add ~/.ssh/id_ed25519_production
# Add key with time limit (5 hours)
ssh-add -t 18000 ~/.ssh/id_ed25519
# Add with confirmation (macOS)
ssh-add -K ~/.ssh/id_ed25519
# =============================================================================
# LIST KEYS
# =============================================================================
# List all keys in agent
ssh-add -l
# List with fingerprints
ssh-add -l -E md5
ssh-add -l -E sha256
# List all (including those not in agent but with keys available)
ssh-add -L
# =============================================================================
# REMOVE KEYS
# =============================================================================
# Remove specific key
ssh-add -d ~/.ssh/id_ed25519
# Remove all keys
ssh-add -D
# Remove keys with timeout
ssh-add -t 300 -d ~/.ssh/id_ed25519
# =============================================================================
# LOCK/UNLOCK AGENT
# =============================================================================
# Lock agent with password
ssh-add -x
# Unlock agent
ssh-add -X
# =============================================================================
# AGENT FORWARDING
# =============================================================================
# In SSH config (client)
Host remote-server
HostName server.example.com
ForwardAgent yes
# On command line
ssh -A user@server
# CAUTION: Agent forwarding allows remote server to use your keys
# Only use with trusted servers

Terminal window
# ~/.ssh/config - Production SSH Client Configuration
# =============================================================================
# GLOBAL DEFAULTS
# =============================================================================
Host *
# Security defaults
PasswordAuthentication no
PubkeyAuthentication yes
IdentitiesOnly yes
# Connection settings
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
# Forwarding
ForwardAgent no
ForwardX11 no
ForwardX11Trusted no
# Security
HashKnownHosts yes
VerifyHostKeyDNS yes
StrictHostKeyChecking ask
# Performance
Compression yes
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
# Logging
LogLevel ERROR
# =============================================================================
# PRODUCTION SERVERS
# =============================================================================
Host prod-*
User admin
Port 22
IdentityFile ~/.ssh/id_ed25519_production
ServerAliveInterval 30
ServerAliveCountMax 5
Host prod-web-1
HostName 192.168.1.10
Host prod-web-*
Host prod-web-2
HostName 192.168.1.11
Host prod-web-*
Host prod-db-1
HostName 192.168.1.20
User dbadmin
# =============================================================================
# DEVELOPMENT SERVERS
# =============================================================================
Host dev-*
User developer
Port 22
IdentityFile ~/.ssh/id_ed25519_dev
StrictHostKeyChecking no
Host dev-server
HostName dev.example.com
# =============================================================================
# JUMP HOST / BASTION
# =============================================================================
Host bastion
HostName bastion.example.com
User admin
Port 22
IdentityFile ~/.ssh/id_ed25519_bastion
ForwardAgent yes
Host internal-server
HostName 192.168.1.100
User admin
ProxyJump bastion
# Alternative using ProxyCommand
Host internal-server-alt
HostName 192.168.1.100
User admin
ProxyCommand ssh -W %h:%p bastion
# =============================================================================
# GITHUB / GIT SERVICES
# =============================================================================
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Host gitlab.com
HostName gitlab.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
IdentitiesOnly yes
# =============================================================================
# SPECIAL CONNECTIONS
# =============================================================================
# High latency connections
Host overseas-server
HostName server.example.com
Compression yes
Cipher aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com
# =============================================================================
# CREATE SOCKET DIRECTORY
# =============================================================================
# Run this once:
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets

Fail2Ban Architecture
+------------------------------------------------------------------+
| |
| +---------------+ |
| | Log Files | |
| | (/var/log/) | |
| +---------------+ |
| | |
| v |
| +---------------+ |
| | Fail2Ban | |
| | Server | |
| +---------------+ |
| | |
| +------+------+-------+------+ |
| | | | | | |
| v v v v v |
| +------+------+-------+------+------+ |
| |Filter|Filter|Filter |Filter |Filter | (Regex patterns) |
| |sshd |nginx |apache |postfix|mysql | |
| +------+------+-------+------+------+ |
| | | | | | |
| v v v v v |
| +------+------+-------+------+------+ |
| |Action|Action|Action |Action |Action | (Ban/Unban actions) |
| |iptabl|firew |abuse |notify |cloud | |
| +------+------+-------+------+------+ |
| |
+------------------------------------------------------------------+
Terminal window
# /etc/fail2ban/jail.local - Production Configuration
[DEFAULT]
# Global settings
bantime = 1h
findtime = 10m
maxretry = 5
destemail = admin@example.com
sender = fail2ban@example.com
action = %(action_mwl)s
# Override action defaults
banaction = iptables-multiport
banaction_allports = iptables-allports
# =============================================================================
# SSH JAIL
# =============================================================================
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 24h
findtime = 1h
# Using recidive jail for repeat offenders
[sshd-ddos]
enabled = true
port = ssh
filter = sshd-ddos
logpath = /var/log/auth.log
maxretry = 20
bantime = 1w
findtime = 1d
# =============================================================================
# WEB SERVER JAILS
# =============================================================================
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 1h
[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = /var/log/nginx/access.log
maxretry = 6
bantime = 1h
[nginx-badrequests]
enabled = true
port = http,https
filter = nginx-badrequests
logpath = /var/log/nginx/access.log
maxretry = 3
bantime = 1h
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/apache2/error.log
maxretry = 5
[apache-badbots]
enabled = true
port = http,https
filter = apache-badbots
logpath = /var/log/apache2/access.log
maxretry = 2
[apache-noscript]
enabled = true
port = http,https
filter = apache-noscript
logpath = /var/log/apache2/access.log
maxretry = 6
# =============================================================================
# DATABASE JAILS
# =============================================================================
[mysqld]
enabled = true
port = 3306
filter = mysqld-auth
logpath = /var/log/mysql/error.log
maxretry = 5
bantime = 1h
[postgresql]
enabled = true
port = 5432
filter = postgresql
logpath = /var/log/postgresql/postgresql.log
maxretry = 5
# =============================================================================
# MAIL SERVER JAILS
# =============================================================================
[postfix]
enabled = true
port = smtp,submission,imaps
filter = postfix
logpath = /var/log/mail.log
maxretry = 5
bantime = 1h
[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps
filter = dovecot
logpath = /var/log/mail.log
maxretry = 5
# =============================================================================
# FTP JAILS
# =============================================================================
[vsftpd]
enabled = true
port = ftp,ftp-data,ftps,ftps-data
filter = vsftpd
logpath = /var/log/vsftpd.log
maxretry = 5
bantime = 1h
# =============================================================================
# CUSTOM FILTERS
# =============================================================================
# Example: Protect API endpoints
[nginx-api]
enabled = true
port = http,https
filter = nginx-api
logpath = /var/log/nginx/api-access.log
maxretry = 10
bantime = 30m
findtime = 1m
# Create /etc/fail2ban/filter.d/nginx-api.conf
#[Definition]
#failregex = ^<HOST> .* "POST /api/.*" HTTP/1.1" (401|403|500|502|503|504)
#ignoreregex =
Terminal window
# =============================================================================
# MONITORING
# =============================================================================
# Check status
sudo fail2ban-client status
sudo fail2ban-client status sshd
# View banned IPs
sudo iptables -L -n
sudo fail2ban-client get sshd banned
# View logs
tail -f /var/log/fail2ban/fail2ban.log
# =============================================================================
# BANNING/UNBANNING
# =============================================================================
# Ban IP manually
sudo fail2ban-client set sshd banip 192.168.1.100
# Unban IP
sudo fail2ban-client set sshd unbanip 192.168.1.100
# Unban all
sudo fail2ban-client unban --all
# =============================================================================
# TESTING FILTERS
# =============================================================================
# Test regex against log
fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
# Test with custom log
echo "Failed password for invalid user admin from 192.168.1.100" | \
fail2ban-regex /dev/stdin /etc/fail2ban/filter.d/sshd.conf

Important

  1. SSH Protocol: Always use SSHv2 (Protocol 2)
  2. Key Types: Ed25519 is recommended (fastest, most secure)
  3. Passphrases: Always use passphrases for production keys
  4. Root Login: Always disable PermitRootLogin in production
  5. Password Auth: Disable PasswordAuthentication in production
  6. Port: Change default port 22 (security through obscurity)
  7. Fail2Ban: Configure for SSH and web services
  8. Key Deployment: Use authorized_keys with restrictions
  9. Agent Forwarding: Use carefully, only with trusted servers
  10. Client Config: Use ~/.ssh/config for organization

In this chapter, you learned:

  • ✅ SSH protocol architecture (transport, authentication, connection)
  • ✅ Comprehensive sshd_config hardening
  • ✅ SSH key types (RSA, ECDSA, Ed25519, Ed448)
  • ✅ SSH key generation and deployment
  • ✅ SSH agent management and security
  • ✅ SSH client configuration best practices
  • ✅ Fail2Ban architecture and configuration
  • ✅ SSH security best practices for production

Chapter 33: Intrusion Detection and Fail2Ban


Last Updated: February 2026