Advanced SSH Configuration and Security: Hardening Your Linux Server
SSH (Secure Shell) is the primary method for remotely accessing Linux servers. However, default SSH configurations often leave servers vulnerable to attacks. This comprehensive guide covers advanced SSH security configurations to protect your Linux servers from unauthorized access.
Understanding SSH Security Risks
Common SSH security threats include:
- Brute Force Attacks: Automated password guessing
- Dictionary Attacks: Using common passwords
- Man-in-the-Middle Attacks: Intercepting communications
- Privilege Escalation: Gaining unauthorized access levels
- Weak Authentication: Poor password policies
SSH Key Authentication Setup
Generating SSH Key Pairs
Ed25519 Keys (Recommended)
# Generate Ed25519 key pair (most secure)
ssh-keygen -t ed25519 -C "[email protected]"
# Generate with custom filename
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_server -C "server_access"
# Generate with passphrase protection
ssh-keygen -t ed25519 -C "[email protected]" -N "strong_passphrase"
RSA Keys (Alternative)
# Generate 4096-bit RSA key
ssh-keygen -t rsa -b 4096 -C "[email protected]"
# Generate with specific algorithm
ssh-keygen -t rsa -b 4096 -a 100 -C "[email protected]"
Deploying Public Keys
Method 1: ssh-copy-id (Recommended)
# Copy key to remote server
ssh-copy-id username@server_ip
# Copy specific key
ssh-copy-id -i ~/.ssh/id_ed25519.pub username@server_ip
# Copy to specific port
ssh-copy-id -p 2222 username@server_ip
Method 2: Manual Installation
# Copy public key content
cat ~/.ssh/id_ed25519.pub
# On remote server, create .ssh directory and add key
mkdir -p ~/.ssh
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5... [email protected]" >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
SSH Agent Configuration
Using ssh-agent
# Start ssh-agent
eval "$(ssh-agent -s)"
# Add key to agent
ssh-add ~/.ssh/id_ed25519
# Add key with lifetime (1 hour)
ssh-add -t 3600 ~/.ssh/id_ed25519
# List loaded keys
ssh-add -l
# Remove all keys from agent
ssh-add -D
Automatic Agent Startup
Add to ~/.bashrc or ~/.zshrc:
# Auto-start ssh-agent
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
fi
Advanced SSH Server Configuration
Main Configuration File
Edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
Essential Security Settings
# Change default port (security through obscurity)
Port 2222
# Protocol version (only use SSH v2)
Protocol 2
# Listen addresses (bind to specific IPs)
ListenAddress 0.0.0.0
ListenAddress ::
# Host keys (use strong algorithms)
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
# Ciphers and MACs (use strong algorithms only)
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr
MACs [email protected],[email protected],hmac-sha2-256,hmac-sha2-512
KexAlgorithms [email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
# Authentication settings
PermitRootLogin no
MaxAuthTries 3
MaxSessions 4
MaxStartups 10:30:60
# Password authentication (disable after key setup)
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
# Key authentication
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
# Disable unused authentication methods
KerberosAuthentication no
GSSAPIAuthentication no
UsePAM yes
# X11 and agent forwarding
X11Forwarding no
AllowAgentForwarding yes
AllowTcpForwarding no
PermitTunnel no
# Timeout settings
LoginGraceTime 60
ClientAliveInterval 300
ClientAliveCountMax 2
# Banner and logging
Banner /etc/ssh/banner
LogLevel VERBOSE
SyslogFacility AUTH
# Subsystem
Subsystem sftp /usr/lib/openssh/sftp-server
User and Group Access Control
# Allow specific users only
AllowUsers alice bob charlie
# Allow users from specific groups
AllowGroups ssh-users administrators
# Deny specific users
DenyUsers baduser guest
# Deny specific groups
DenyGroups noremote
Creating SSH User Groups
# Create SSH access group
sudo groupadd ssh-users
# Add users to group
sudo usermod -a -G ssh-users alice
sudo usermod -a -G ssh-users bob
# Verify group membership
groups alice
Conditional Access Rules
# Different rules for different users/hosts
Match User backup
ForceCommand /usr/local/bin/backup-script
PermitTTY no
X11Forwarding no
Match Group sftp-only
ChrootDirectory /home/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
Match Address 192.168.1.0/24
PasswordAuthentication yes
MaxAuthTries 6
Two-Factor Authentication (2FA)
Installing Google Authenticator
# Install libpam-google-authenticator
sudo apt update
sudo apt install libpam-google-authenticator -y
Configure Google Authenticator
# Run setup for each user
google-authenticator
# Answer setup questions:
# Do you want authentication tokens to be time-based? (y/n) y
# Do you want me to update your "~/.google_authenticator" file? (y/n) y
# Do you want to disallow multiple uses of the same authentication token? (y/n) y
# Do you want to increase the original generation time window? (y/n) n
# Do you want to enable rate-limiting? (y/n) y
Configure PAM for 2FA
Edit /etc/pam.d/sshd:
# Add at the beginning
auth required pam_google_authenticator.so
# Comment out common-auth for key-only 2FA
#@include common-auth
Update SSH Configuration for 2FA
Add to /etc/ssh/sshd_config:
# Enable challenge response for 2FA
ChallengeResponseAuthentication yes
# Require both key and 2FA
AuthenticationMethods publickey,keyboard-interactive
Restart SSH service:
sudo systemctl restart sshd
Intrusion Detection with Fail2ban
Installing Fail2ban
sudo apt update
sudo apt install fail2ban -y
Basic Fail2ban Configuration
Create /etc/fail2ban/jail.local:
[DEFAULT]
# Ban settings
bantime = 3600
findtime = 600
maxretry = 3
# Email notifications
destemail = [email protected]
sendername = Fail2Ban
mta = sendmail
# Default action
action = %(action_mwl)s
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
[sshd-ddos]
enabled = true
port = 2222
filter = sshd-ddos
logpath = /var/log/auth.log
maxretry = 6
bantime = 3600
findtime = 60
Custom Fail2ban Filters
Create /etc/fail2ban/filter.d/sshd-custom.conf:
[Definition]
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error|failed) for .* from <HOST>( via \S+)?\s*$
^%(__prefix_line)s(?:error: )?Received disconnect from <HOST>: 3: .*: Auth fail$
^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers$
^%(__prefix_line)sUser .+ from <HOST> not allowed because listed in DenyUsers$
^%(__prefix_line)sUser .+ from <HOST> not allowed because not in any group$
^%(__prefix_line)srefused connect from \S+ \(<HOST>\)$
^%(__prefix_line)sReceived disconnect from <HOST>: 11: Bye Bye$
^%(__prefix_line)sConnection closed by <HOST> \[preauth\]$
^%(__prefix_line)sDisconnected from <HOST> \[preauth\]$
ignoreregex =
Advanced Fail2ban Actions
Create custom action in /etc/fail2ban/action.d/iptables-custom.conf:
[Definition]
actionstart = iptables -N f2b-<name>
iptables -A f2b-<name> -j RETURN
iptables -I INPUT -p <protocol> --dport <port> -j f2b-<name>
actionstop = iptables -D INPUT -p <protocol> --dport <port> -j f2b-<name>
iptables -X f2b-<name>
actioncheck = iptables -n -L INPUT | grep -q 'f2b-<name>[ \t]'
actionban = iptables -I f2b-<name> 1 -s <ip> -j REJECT --reject-with icmp-port-unreachable
# Send notification
echo "$(date): Banned IP <ip> for jail <name>" >> /var/log/fail2ban-custom.log
actionunban = iptables -D f2b-<name> -s <ip> -j REJECT --reject-with icmp-port-unreachable
echo "$(date): Unbanned IP <ip> for jail <name>" >> /var/log/fail2ban-custom.log
[Init]
name = default
port = ssh
protocol = tcp
chain = INPUT
Fail2ban Management Commands
# Start and enable fail2ban
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
# Check status
sudo fail2ban-client status
# Check specific jail
sudo fail2ban-client status sshd
# Unban an IP
sudo fail2ban-client set sshd unbanip 192.168.1.100
# Ban an IP manually
sudo fail2ban-client set sshd banip 192.168.1.100
# Reload configuration
sudo fail2ban-client reload
SSH Connection Monitoring
Real-time Connection Monitoring
Script for Active SSH Sessions
Create /usr/local/bin/ssh-monitor.sh:
#!/bin/bash
# SSH Connection Monitor
echo "=== Active SSH Sessions ==="
echo "Current time: $(date)"
echo
# Show active SSH connections
netstat -tnpa | grep 'ESTABLISHED.*sshd' | while read line; do
local_addr=$(echo $line | awk '{print $4}')
foreign_addr=$(echo $line | awk '{print $5}')
pid=$(echo $line | awk '{print $7}' | cut -d'/' -f1)
if [ ! -z "$pid" ]; then
user=$(ps -o user= -p $pid 2>/dev/null)
start_time=$(ps -o lstart= -p $pid 2>/dev/null)
echo "User: $user | From: $foreign_addr | Started: $start_time"
fi
done
echo
echo "=== Recent SSH Login Attempts ==="
tail -n 20 /var/log/auth.log | grep sshd | grep -E '(Accepted|Failed)'
echo
echo "=== Failed Login Summary (Last 100 entries) ==="
grep "Failed password" /var/log/auth.log | tail -100 | \
awk '{print $11}' | sort | uniq -c | sort -nr | head -10
Make it executable:
sudo chmod +x /usr/local/bin/ssh-monitor.sh
Automated Monitoring with Logwatch
Install and configure logwatch:
sudo apt install logwatch -y
# Configure logwatch for SSH monitoring
sudo tee /etc/logwatch/conf/services/sshd.conf <<EOF
# SSH monitoring configuration
Detail = High
Title = "SSH Activity Report"
EOF
SSH Logging Enhancement
Custom SSH Logging
Add to /etc/ssh/sshd_config:
# Enhanced logging
LogLevel VERBOSE
SyslogFacility AUTH
# Log successful logins
UsePAM yes
Rsyslog Configuration for SSH
Create /etc/rsyslog.d/ssh.conf:
# SSH specific logging
if $programname == 'sshd' then /var/log/sshd.log
& stop
Restart rsyslog:
sudo systemctl restart rsyslog
SSH Audit Script
Create /usr/local/bin/ssh-audit.sh:
#!/bin/bash
# SSH Security Audit Script
echo "=== SSH Security Audit Report ==="
echo "Generated: $(date)"
echo "Hostname: $(hostname)"
echo
# Check SSH configuration
echo "=== SSH Configuration Review ==="
echo "SSH Port: $(grep "^Port" /etc/ssh/sshd_config || echo "Default (22)")"
echo "Root Login: $(grep "^PermitRootLogin" /etc/ssh/sshd_config || echo "Default (yes)")"
echo "Password Auth: $(grep "^PasswordAuthentication" /etc/ssh/sshd_config || echo "Default (yes)")"
echo "Key Auth: $(grep "^PubkeyAuthentication" /etc/ssh/sshd_config || echo "Default (yes)")"
echo
# Check active sessions
echo "=== Active SSH Sessions ==="
who | grep pts
echo
# Check recent logins
echo "=== Recent Successful Logins ==="
last -n 10 | grep pts
echo
# Check failed login attempts
echo "=== Recent Failed Login Attempts ==="
grep "Failed password" /var/log/auth.log | tail -5
echo
# Check SSH keys
echo "=== Authorized SSH Keys ==="
for user in $(awk -F: '($3 >= 1000) {print $1}' /etc/passwd); do
if [ -f "/home/$user/.ssh/authorized_keys" ]; then
echo "User: $user"
wc -l "/home/$user/.ssh/authorized_keys"
fi
done
echo
# Check fail2ban status
if command -v fail2ban-client >/dev/null 2>&1; then
echo "=== Fail2ban Status ==="
fail2ban-client status sshd 2>/dev/null || echo "Fail2ban not running for SSH"
fi
SSH Tunneling and Port Forwarding
Local Port Forwarding
# Forward local port to remote service
ssh -L 8080:localhost:80 user@remote-server
# Forward to different host through SSH server
ssh -L 8080:database-server:3306 user@ssh-gateway
# Run in background
ssh -f -N -L 8080:localhost:80 user@remote-server
Remote Port Forwarding
# Forward remote port to local service
ssh -R 8080:localhost:80 user@remote-server
# Multiple port forwards
ssh -R 8080:localhost:80 -R 3306:localhost:3306 user@remote-server
Dynamic Port Forwarding (SOCKS Proxy)
# Create SOCKS proxy
ssh -D 1080 user@remote-server
# Use with applications
curl --socks5 localhost:1080 http://example.com
SSH Jump Hosts (ProxyJump)
Configure in ~/.ssh/config:
# Direct connection to internal server through bastion
Host internal-server
HostName 10.0.1.100
User admin
ProxyJump bastion-host
Host bastion-host
HostName bastion.example.com
User jump-user
Port 2222
SSH Client Configuration
SSH Config File Organization
Create ~/.ssh/config:
# Global defaults
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
TCPKeepAlive yes
Compression yes
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h:%p
ControlPersist 10m
# Production servers
Host prod-*
User admin
Port 2222
IdentityFile ~/.ssh/id_ed25519_prod
StrictHostKeyChecking yes
UserKnownHostsFile ~/.ssh/known_hosts_prod
# Development servers
Host dev-*
User developer
Port 22
IdentityFile ~/.ssh/id_ed25519_dev
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
LogLevel QUIET
# Specific server configurations
Host web-server
HostName web.example.com
User webadmin
Port 2222
IdentityFile ~/.ssh/id_ed25519_web
LocalForward 8080 localhost:80
Host database
HostName db.internal.com
User dbadmin
ProxyJump bastion
LocalForward 5432 localhost:5432
SSH Connection Multiplexing
# Create socket directory
mkdir -p ~/.ssh/sockets
# Test connection multiplexing
ssh -O check web-server
ssh -O exit web-server
Troubleshooting SSH Issues
Common SSH Problems
Debug SSH Connections
# Client-side debugging
ssh -v user@server
ssh -vv user@server # More verbose
ssh -vvv user@server # Very verbose
# Server-side debugging
sudo /usr/sbin/sshd -d -p 2223
Check SSH Service Status
# Service status
systemctl status sshd
# Check configuration syntax
sudo sshd -t
# Check listening ports
sudo netstat -tlnp | grep sshd
ss -tlnp | grep sshd
Permission Issues
# Fix SSH directory permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_*
chmod 644 ~/.ssh/id_*.pub
chmod 644 ~/.ssh/known_hosts
chmod 600 ~/.ssh/config
SSH Security Testing
Test SSH Configuration
# Test with ssh-audit (install first)
sudo apt install ssh-audit -y
ssh-audit localhost
# Test specific algorithms
ssh -o KexAlgorithms=diffie-hellman-group1-sha1 user@server
Security Scanning
# Nmap SSH scan
nmap -sV -p 22,2222 target-server
# Check for weak ciphers
nmap --script ssh2-enum-algos target-server
Best Practices Summary
Security Checklist
- Change default SSH port
- Disable root login
- Use key authentication only
- Enable 2FA for critical systems
- Configure fail2ban
- Implement connection monitoring
- Regular security audits
- Keep SSH updated
- Use strong key algorithms (Ed25519)
- Implement access controls
Maintenance Tasks
# Weekly security review script
#!/bin/bash
echo "Weekly SSH Security Review - $(date)"
echo "=================================="
# Check for updates
apt list --upgradable | grep openssh
# Review recent connections
echo "Recent SSH connections:"
last -n 20 | grep ssh
# Check fail2ban status
fail2ban-client status sshd
# Review SSH configuration
echo "Current SSH configuration:"
sshd -T | grep -E "(port|permitrootlogin|passwordauthentication|pubkeyauthentication)"
# Check for weak keys
echo "Checking for weak SSH keys..."
find /home -name "id_rsa" -exec ssh-keygen -l -f {} \; 2>/dev/null | grep " 1024 "
Conclusion
Implementing advanced SSH security measures is crucial for protecting your Linux servers. This configuration provides multiple layers of security including strong authentication, intrusion detection, monitoring, and access controls.
Key security improvements achieved:
- Eliminated password-based authentication vulnerabilities
- Implemented multi-factor authentication
- Added automated intrusion detection and prevention
- Enhanced logging and monitoring capabilities
- Configured secure connection policies
Regular maintenance and monitoring ensure your SSH security remains effective against evolving threats. Always test configurations in a development environment before applying to production systems.
Remember that security is an ongoing process - regularly review logs, update configurations, and stay informed about new SSH security best practices and vulnerabilities.
