How to Secure SSH Access on Linux Servers

SSH (Secure Shell) is the primary method for remotely accessing Linux servers, making it a critical component to secure. An improperly configured SSH server is a major security vulnerability that can lead to unauthorized access, data breaches, and compromised systems. This comprehensive guide will walk you through implementing SSH security best practices to protect your servers.

Understanding SSH Security Risks

Before implementing security measures, understand common attack vectors:

  • Brute Force Attacks: Automated attempts to guess passwords
  • Default Credentials: Using common username/password combinations
  • Unpatched Vulnerabilities: Exploiting known SSH software flaws
  • Weak Encryption: Using outdated cryptographic algorithms
  • Man-in-the-Middle: Intercepting SSH connections
  • Compromised Keys: Stolen or leaked private keys

Step 1: Use SSH Keys Instead of Passwords

SSH key authentication is significantly more secure than password authentication.

Generate SSH Key Pair

On your local machine (not the server):

# Generate a strong RSA key (4096 bits)
ssh-keygen -t rsa -b 4096 -C "[email protected]"

## Or use Ed25519 (modern, more secure)
ssh-keygen -t ed25519 -C "[email protected]"

## Follow prompts:
## - Accept default location (~/.ssh/id_rsa or ~/.ssh/id_ed25519)
## - Enter a strong passphrase (highly recommended)

Copy Public Key to Server

## Use ssh-copy-id (easiest method)
ssh-copy-id username@server_ip

## Or manually copy
cat ~/.ssh/id_rsa.pub | ssh username@server_ip "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

## Set correct permissions on server
ssh username@server_ip "chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"

Test Key Authentication

## Test SSH connection with key
ssh username@server_ip

## You should connect without password (or only passphrase if set)

Step 2: Harden SSH Configuration

Edit the SSH daemon configuration file with secure settings.

Backup Original Configuration

sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup

Edit SSH Configuration

sudo nano /etc/ssh/sshd_config
## Disable root login
PermitRootLogin no

## Disable password authentication (after setting up keys!)
PasswordAuthentication no
PermitEmptyPasswords no

## Disable challenge-response authentication
ChallengeResponseAuthentication no

## Enable public key authentication
PubkeyAuthentication yes

## Limit authentication attempts
MaxAuthTries 3

## Disable X11 forwarding (unless needed)
X11Forwarding no

## Disable TCP forwarding (if not needed)
AllowTcpForwarding no

## Set idle timeout (15 minutes)
ClientAliveInterval 300
ClientAliveCountMax 2

## Disable host-based authentication
HostbasedAuthentication no

## Use only strong ciphers and algorithms
Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr
MACs [email protected],[email protected],hmac-sha2-512,hmac-sha2-256
KexAlgorithms [email protected],diffie-hellman-group-exchange-sha256

## Use protocol 2 only (should be default)
Protocol 2

## Set banner (optional)
Banner /etc/ssh/banner

## Enable strict mode
StrictModes yes

## Limit user access (replace with actual usernames)
AllowUsers username1 username2

## Or limit groups
AllowGroups sshusers

Apply Configuration Changes

## Test configuration for syntax errors
sudo sshd -t

## If no errors, restart SSH service
sudo systemctl restart sshd

## Or on older systems
sudo service ssh restart

Important: Keep your current SSH session open and test new connections in a separate terminal to ensure you haven’t locked yourself out!

Step 3: Change Default SSH Port

Changing the default port (22) reduces automated attacks.

Choose New Port

## Edit sshd_config
sudo nano /etc/ssh/sshd_config

## Change Port line
Port 2222  # Choose a port between 1024-65535

## Save and test configuration
sudo sshd -t

## Restart SSH
sudo systemctl restart sshd

Update Firewall Rules

## UFW (Ubuntu/Debian)
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp

## firewalld (CentOS/RHEL)
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --reload

## iptables
sudo iptables -A INPUT -p tcp --dport 2222 -j ACCEPT
sudo iptables -D INPUT -p tcp --dport 22 -j ACCEPT

Connect with New Port

ssh -p 2222 username@server_ip

Step 4: Implement Fail2Ban

Fail2Ban automatically blocks IP addresses after failed login attempts.

Install Fail2Ban

## Ubuntu/Debian
sudo apt update
sudo apt install fail2ban

## CentOS/RHEL
sudo yum install epel-release
sudo yum install fail2ban

## Start and enable service
sudo systemctl start fail2ban
sudo systemctl enable fail2ban

Configure Fail2Ban for SSH

## Create local configuration
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

## Edit configuration
sudo nano /etc/fail2ban/jail.local

Add or modify SSH jail configuration:

[sshd]
enabled = true
port = 2222  # Use your SSH port
filter = sshd
logpath = /var/log/auth.log  # Ubuntu/Debian
## logpath = /var/log/secure  # CentOS/RHEL
maxretry = 3
findtime = 600
bantime = 3600

Restart Fail2Ban

sudo systemctl restart fail2ban

## Check status
sudo fail2ban-client status

## Check SSH jail status
sudo fail2ban-client status sshd

View Banned IPs

## List banned IPs
sudo fail2ban-client status sshd

## Manually ban IP
sudo fail2ban-client set sshd banip 192.168.1.100

## Manually unban IP
sudo fail2ban-client set sshd unbanip 192.168.1.100

Step 5: Use Two-Factor Authentication

Add an extra security layer with 2FA.

Install Google Authenticator

## Ubuntu/Debian
sudo apt install libpam-google-authenticator

## CentOS/RHEL
sudo yum install google-authenticator

Configure Google Authenticator

## Run as the user who will log in
google-authenticator

## Answer prompts:
## - Do you want authentication tokens to be time-based? YES
## - Update .google_authenticator file? YES
## - Disallow multiple uses? YES
## - Increase window from 3 to 4? NO (better security)
## - Enable rate-limiting? YES

## Scan QR code with authenticator app or save emergency codes

Configure PAM

## Edit PAM sshd configuration
sudo nano /etc/pam.d/sshd

## Add at the top
auth required pam_google_authenticator.so

Configure SSH for 2FA

sudo nano /etc/ssh/sshd_config

## Add or modify these lines
ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

Restart SSH

sudo systemctl restart sshd

Now SSH login requires both your SSH key and TOTP code.

Step 6: Implement IP Whitelisting

Restrict SSH access to specific IP addresses.

Using SSH Configuration

sudo nano /etc/ssh/sshd_config

## Allow only specific IPs
AllowUsers [email protected]
AllowUsers [email protected]/24

## Or use Match blocks
Match Address 192.168.1.0/24
    AllowUsers *

Using Firewall Rules

UFW:

## Deny all SSH by default
sudo ufw default deny incoming

## Allow SSH from specific IP
sudo ufw allow from 192.168.1.100 to any port 2222

## Allow from IP range
sudo ufw allow from 192.168.1.0/24 to any port 2222

iptables:

## Accept from specific IP
sudo iptables -A INPUT -p tcp -s 192.168.1.100 --dport 2222 -j ACCEPT

## Drop all other SSH connections
sudo iptables -A INPUT -p tcp --dport 2222 -j DROP

## Save rules
sudo netfilter-persistent save

Step 7: Monitor SSH Access

Regular monitoring helps detect unauthorized access attempts.

Check Failed Login Attempts

## Ubuntu/Debian
sudo grep "Failed password" /var/log/auth.log | tail -20

## CentOS/RHEL
sudo grep "Failed password" /var/log/secure | tail -20

## Count failed attempts by IP
sudo grep "Failed password" /var/log/auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn

Monitor Successful Logins

## Last successful logins
last -a | head -20

## Currently logged in users
who

## Login history for specific user
last username

Set Up Real-Time Alerts

Create a monitoring script:

sudo nano /usr/local/bin/ssh-monitor.sh

Add content:

#!/bin/bash
## SSH login alerting script

LOGFILE="/var/log/auth.log"
ALERT_EMAIL="[email protected]"

tail -Fn0 $LOGFILE | while read line; do
    echo "$line" | grep "Accepted publickey" && {
        IP=$(echo "$line" | awk '{print $(NF-3)}')
        USER=$(echo "$line" | awk '{print $9}')
        echo "SSH login: $USER from $IP at $(date)" | mail -s "SSH Login Alert" $ALERT_EMAIL
    }
    
    echo "$line" | grep "Failed password" && {
        IP=$(echo "$line" | awk '{print $(NF-3)}')
        echo "Failed SSH login attempt from $IP at $(date)" | mail -s "SSH Attack Alert" $ALERT_EMAIL
    }
done

Make executable and run:

sudo chmod +x /usr/local/bin/ssh-monitor.sh
sudo /usr/local/bin/ssh-monitor.sh &

Step 8: Use SSH Bastion/Jump Hosts

For production environments, use bastion hosts for additional security.

Architecture

Internet → Bastion Host (Public) → Internal Servers (Private)

Configure Jump Host

## Connect through bastion
ssh -J bastion_user@bastion_host:bastion_port user@internal_server

## Or in ~/.ssh/config
Host internal-server
    HostName 10.0.1.10
    User username
    ProxyJump bastion_user@bastion_host:2222

SSH Config for Convenience

nano ~/.ssh/config

## Add configuration
Host bastion
    HostName bastion.example.com
    User bastion_user
    Port 2222
    IdentityFile ~/.ssh/bastion_key

Host internal-*
    ProxyJump bastion
    User internal_user
    IdentityFile ~/.ssh/internal_key

Host internal-web
    HostName 10.0.1.10

Host internal-db
    HostName 10.0.1.20

Now connect easily:

ssh internal-web
ssh internal-db

Step 9: Regular Security Audits

Perform regular security checks to maintain SSH security.

Security Checklist

#!/bin/bash
## ssh-security-audit.sh

echo "SSH Security Audit - $(date)"
echo "=================================="

echo -e "\n1. Check for root login:"
sudo grep "^PermitRootLogin" /etc/ssh/sshd_config

echo -e "\n2. Check password authentication:"
sudo grep "^PasswordAuthentication" /etc/ssh/sshd_config

echo -e "\n3. Check SSH protocol:"
sudo grep "^Protocol" /etc/ssh/sshd_config

echo -e "\n4. List authorized users:"
sudo grep "^AllowUsers" /etc/ssh/sshd_config

echo -e "\n5. Check SSH port:"
sudo grep "^Port" /etc/ssh/sshd_config

echo -e "\n6. Recent failed login attempts:"
sudo grep "Failed password" /var/log/auth.log | tail -10 | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn

echo -e "\n7. Check Fail2Ban status:"
sudo fail2ban-client status sshd 2>/dev/null || echo "Fail2Ban not installed"

echo -e "\n8. List SSH keys:"
find ~/.ssh -name "authorized_keys" -exec wc -l {} \;

echo -e "\n9. Check for weak ciphers:"
sudo sshd -T | grep ciphers

Run Security Audit

chmod +x ssh-security-audit.sh
./ssh-security-audit.sh > ssh-audit-$(date +%Y%m%d).txt

Step 10: Keep SSH Updated

Regular updates patch security vulnerabilities.

Check SSH Version

ssh -V

Update SSH

## Ubuntu/Debian
sudo apt update
sudo apt upgrade openssh-server openssh-client

## CentOS/RHEL
sudo yum update openssh openssh-server

## Restart SSH after update
sudo systemctl restart sshd

Subscribe to Security Advisories

Advanced Security Measures

Use Port Knocking

Add an extra layer by requiring a specific sequence of port connections before SSH becomes accessible:

## Install knockd
sudo apt install knockd

## Configure /etc/knockd.conf
[options]
    logfile = /var/log/knockd.log

[openSSH]
    sequence    = 7000,8000,9000
    seq_timeout = 15
    command     = /sbin/iptables -A INPUT -s %IP% -p tcp --dport 2222 -j ACCEPT
    tcpflags    = syn

[closeSSH]
    sequence    = 9000,8000,7000
    seq_timeout = 15
    command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 2222 -j ACCEPT
    tcpflags    = syn

Implement Certificate-Based Authentication

For large deployments, use SSH certificates instead of managing individual keys.

Use Hardware Security Keys

Consider YubiKeys or similar hardware tokens for SSH authentication.

Common Mistakes to Avoid

  1. Locking yourself out: Always test in a separate session
  2. Disabling password auth before setting up keys: Set up keys first!
  3. Not backing up configuration: Always keep backups
  4. Using weak passphrases: Use strong passphrases on private keys
  5. Forgetting firewall rules: Update firewall when changing ports
  6. Not monitoring logs: Regular log review catches issues early
  7. Reusing SSH keys: Generate unique keys per device/user

Emergency Access Recovery

If you lock yourself out:

  1. Cloud Providers: Use web console or recovery console
  2. Physical Access: Boot into single-user mode
  3. KVM/IPMI: Use out-of-band management interfaces
  4. Backup Admin Account: Always maintain a backup access method

Conclusion

Securing SSH access is not optional—it’s essential for protecting your Linux servers. By following this guide:

  1. ✅ Use SSH keys instead of passwords
  2. ✅ Harden SSH configuration with secure settings
  3. ✅ Change default SSH port
  4. ✅ Implement Fail2Ban for automatic blocking
  5. ✅ Enable two-factor authentication
  6. ✅ Restrict access with IP whitelisting
  7. ✅ Monitor access logs regularly
  8. ✅ Use bastion hosts for production
  9. ✅ Perform regular security audits
  10. ✅ Keep SSH software updated

Remember: security is an ongoing process, not a one-time setup. Regularly review and update your security measures, stay informed about new vulnerabilities, and always test changes before applying them to production systems.

Final tip: Document your SSH security configuration and keep it updated. Future you (or your team) will thank you when troubleshooting or updating security measures.

Thank you for reading! If you have any feedback or comments, please send them to [email protected].