Back to all posts

Advanced SSH Configuration and Security: Hardening Your Linux Server

Comprehensive guide to securing SSH access with key authentication, fail2ban, advanced configurations, and monitoring best practices.

Advanced SSH Configuration and Security: Hardening Your Linux Server

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

  1. Change default SSH port
  2. Disable root login
  3. Use key authentication only
  4. Enable 2FA for critical systems
  5. Configure fail2ban
  6. Implement connection monitoring
  7. Regular security audits
  8. Keep SSH updated
  9. Use strong key algorithms (Ed25519)
  10. 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.