Skip to content

Ssh_hardening

SSH (Secure Shell) is the primary method for remote Linux administration. Securing SSH is critical for system security. This chapter covers SSH key-based authentication, hardening configurations, jump hosts, and protection against common attacks.


Terminal window
# Generate ED25519 key (recommended)
ssh-keygen -t ed25519 -C "user@hostname"
# Generate RSA key (4096 bit, for compatibility)
ssh-keygen -t rsa -b 4096 -C "user@hostname"
# Generate with comment
ssh-keygen -t ed25519 -C "admin@workstation"
# Different key filename
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work
# Add passphrase (highly recommended)
# Enter passphrase when prompted
Terminal window
# Method 1: ssh-copy-id (easiest)
ssh-copy-id user@server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
# Method 2: Manual
# Copy public key to server
cat ~/.ssh/id_ed25519.pub
# Then add to ~/.ssh/authorized_keys on server
# Method 3: Direct pipe
cat ~/.ssh/id_ed25519.pub | ssh user@server 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'
Terminal window
# Start SSH agent
eval "$(ssh-agent -s)"
# Add key to agent
ssh-add ~/.ssh/id_ed25519
# List keys in agent
ssh-add -l
# Add with timeout (security)
ssh-add -t 1h ~/.ssh/id_ed25519 # 1 hour timeout
# Add all keys
ssh-add
# Remove all keys
ssh-add -D

~/.ssh/config
Host server1
HostName server1.example.com
User admin
Port 22
IdentityFile ~/.ssh/id_ed25519
Host server2
HostName 192.168.1.100
User root
Port 2222
Host *
# Apply to all hosts
AddKeysToAgent yes
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
ServerAliveCountMax 3
StrictHostKeyChecking ask
UserKnownHostsFile ~/.ssh/known_hosts
# Jump host configuration
Host internal
HostName internal.example.com
ProxyJump jump.example.com
# Using ProxyJump (modern)
Host db-server
HostName 192.168.1.50
User dbadmin
ProxyJump admin@jump-server
/etc/ssh/sshd_config
# Disable root login
PermitRootLogin no
# Disable password authentication
PasswordAuthentication no
PermitEmptyPasswords no
# Use only strong ciphers
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
# Key exchange
KexAlgorithms curve25519-sha256,ecdh-sha2-nistp256
# MAC selection
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Disable unused authentication methods
HostbasedAuthentication no
PubkeyAuthentication yes
KerberosAuthentication no
GSSAPIAuthentication no
# Session settings
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 10
# Banner
Banner /etc/ssh/banner
# Allow specific users/groups
AllowUsers admin deploy automation
AllowGroups sshusers
# Disable protocol 1
Protocol 2
# X11 forwarding
X11Forwarding no
# Agent forwarding (use with caution)
AllowAgentForwarding no
Terminal window
# Always use Protocol 2
Protocol 2
# Set strong key bits
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
# Logging
LogLevel VERBOSE
# Subsystem
Subsystem sftp /usr/lib/ssh/ssh-sftp-server
# Chroot directory for SFTP
# (see SFTP section)

Terminal window
# Install fail2ban
sudo pacman -S fail2ban
# Create local configuration
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
destemail = admin@example.com
sender = fail2ban@example.com
action = %(action_mwl)s
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 24h
Terminal window
# Rate limiting with firewalld
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-rich-rule='rule service name="ssh" accept limit value="3/m"'
sudo firewall-cmd --reload
# Allow only specific IPs
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="ssh" accept'
Terminal window
# Rate limit SSH connections
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
# Allow SSH from specific IP only
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j DROP

Terminal window
# On CA server (your laptop), generate CA key
ssh-keygen -t ed25519 -C "ca@home"
# Configure SSH to use CA
# ~/.ssh/config
Host *
TrustedUserCAKeys ~/.ssh/ca.pub
# On servers, add CA to authorized_keys
# /etc/ssh/sshd_config
TrustedUserCAKeys /etc/ssh/ca.pub
# Sign user key
ssh-keygen -s ~/.ssh/ca -I "user@workstation" -n deploy,automation -V +52w ~/.ssh/id_ed25519.pub
# User can now SSH to any server with that principal
# No need to copy keys to each server

Terminal window
# Architecture:
# Client -> Jump Host (Bastion) -> Internal Network
# sshd_config on jump host
# Allow only key-based auth
PasswordAuthentication no
# SSH config for jump host
Host internal-server
HostName 10.0.1.50
User admin
ProxyJump admin@jump.example.com
# Or using ProxyCommand (older method)
Host internal-server
HostName 10.0.1.50
User admin
ProxyCommand ssh -W %h:%p admin@jump.example.com
Terminal window
# Install Session Manager plugin
# For Linux:
curl -Lo /tmp/session-manager-plugin.rpm https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm
sudo rpm -i /tmp/session-manager-plugin.rpm
# Connect to instance (without SSH)
aws ssm start-session --target i-1234567890abcdef0
# SSH through Session Manager
ssh -i key.pem admin@i-1234567890abcdef0

Terminal window
# Access internal service through SSH tunnel
# Local:8080 -> SSH Server -> Remote:80
ssh -L 8080:localhost:80 user@server
ssh -L 8080:internal-web:80 user@jump-server
# Example: Access internal database
ssh -L 5432:localhost:5432 user@db-server
# Now connect to localhost:5432 to reach db-server:5432
Terminal window
# Create SOCKS proxy on local port 1080
ssh -D 1080 user@server
# Configure browser to use SOCKS5 proxy localhost:1080
# All traffic through SSH tunnel
Terminal window
# Allow external access to internal service
# Remote:8080 -> SSH -> Local:80
ssh -R 8080:localhost:80 user@public-server
# Expose local web server to internet
ssh -R 80:localhost:80 -R 443:localhost:443 user@public-server

Terminal window
# /etc/ssh/sshd_config with SFTP only
Subsystem sftp internal-sftp
Match Group sftponly
ChrootDirectory /home/%u
AllowTcpForwarding no
X11Forwarding no
ForceCommand internal-sftp
# Create SFTP-only user
sudo groupadd sftponly
sudo useradd -m -g sftponly -s /usr/bin/sftp-server sftpuser
sudo passwd sftpuser
# Set directory permissions
sudo chown root:root /home/sftpuser
sudo chmod 755 /home/sftpuser
sudo mkdir /home/sftpuser/uploads
sudo chown sftpuser:sftponly /home/sftpuser/uploads

Terminal window
# Connection refused
# Check service: sudo systemctl status sshd
# Check firewall: sudo firewall-cmd --list-all
# Permission denied (publickey)
# Check key in authorized_keys
# Check permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_rsa
# Too many authentication failures
# Add to config: MaxAuthTries 50 or use IdentitiesOnly yes
# Host key changed
# Remove old key: ssh-keygen -R hostname
# Or edit ~/.ssh/known_hosts
# Key not accepted
# Check SELinux: restorecon -Rv ~/.ssh
# Check home directory permissions
Terminal window
# Verbose output
ssh -v user@server
ssh -vv user@server # More verbose
ssh -vvv user@server # Most verbose
# Test authentication
ssh -v -o PreferredAuthentications=publickey user@server

/etc/ssh/sshd_config
# 1. Use key-based authentication (disable passwords)
PasswordAuthentication no
# 2. Disable root login
PermitRootLogin no
# 3. Change default port
Port 2222
# 4. Use strong ciphers
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
# 5. Implement fail2ban
# 6. Use firewall to limit access
# Only allow specific IPs
# 7. Keep SSH updated
sudo pacman -Syu openssh
# 8. Use SSH certificates for large deployments
# 9. Monitor auth logs
tail -f /var/log/auth.log | grep sshd
# 10. Use jump hosts for internal access
# 11. Rotate keys regularly
# Generate new keys annually
# 12. Use two-factor authentication
# Google Authenticator or Duo
Terminal window
# Install google-authenticator
sudo pacman -S google-authenticator
# Configure user
google-authenticator
# Add to sshd
# /etc/pam.d/sshd
auth required pam_google_authenticator.so
# /etc/ssh/sshd_config
ChallengeResponseAuthentication yes
AuthenticationMethods password,keyboard-interactive

ssh-wrapper.sh
#!/bin/bash
# Add key to agent if not already
if ! ssh-add -l > /dev/null 2>&1; then
ssh-add
fi
# Connect to server
server=$1
shift
ssh -o "ServerAliveInterval=60" \
-o "ServerAliveCountMax=3" \
"$server" "$@"
manage_ssh_keys.sh
#!/bin/bash
KEY_DIR="$HOME/.ssh"
DAYS_OLD=90
rotate_keys() {
local key_type=$1
local key_name="id_${key_type}"
# Backup old key
if [ -f "$KEY_DIR/$key_name" ]; then
mv "$KEY_DIR/$key_name" "$KEY_DIR/${key_name}.old"
mv "$KEY_DIR/${key_name}.pub" "$KEY_DIR/${key_name}.pub.old"
fi
# Generate new key
ssh-keygen -t "$key_type" -f "$KEY_DIR/$key_name" -C "$USER@$(hostname)"
echo "New key generated. Deploy to servers:"
cat "$KEY_DIR/${key_name}.pub"
}
case "$1" in
rotate)
rotate_keys ed25519
;;
list)
ls -la "$KEY_DIR"/id_*
;;
deploy)
server=$2
cat "$KEY_DIR/id_ed25519.pub" | ssh "$server" 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'
;;
*)
echo "Usage: $0 {rotate|list|deploy <server>}"
;;
esac

In this chapter, you learned:

  • ✅ SSH key-based authentication
  • ✅ Generating ED25519/RSA keys
  • ✅ SSH agent and key management
  • ✅ Client and server hardening
  • ✅ fail2ban for brute force protection
  • ✅ Firewalld/iptables rate limiting
  • ✅ SSH certificates for scale
  • ✅ Jump hosts and bastion servers
  • ✅ Port forwarding and tunneling
  • ✅ SFTP configuration
  • ✅ Two-factor authentication
  • ✅ Security best practices checklist

Chapter 17: High Availability and Load Balancing


Last Updated: February 2026