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
Recommended Security Settings
## 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
- OpenSSH Security Advisories
- Your distribution’s security mailing list
- CVE databases for SSH vulnerabilities
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
- Locking yourself out: Always test in a separate session
- Disabling password auth before setting up keys: Set up keys first!
- Not backing up configuration: Always keep backups
- Using weak passphrases: Use strong passphrases on private keys
- Forgetting firewall rules: Update firewall when changing ports
- Not monitoring logs: Regular log review catches issues early
- Reusing SSH keys: Generate unique keys per device/user
Emergency Access Recovery
If you lock yourself out:
- Cloud Providers: Use web console or recovery console
- Physical Access: Boot into single-user mode
- KVM/IPMI: Use out-of-band management interfaces
- Backup Admin Account: Always maintain a backup access method
Related Articles
- What is Cyber Essentials, Cyber Essentials Plus and how do
- Essential Penetration Testing Tools
- Raspberry Pi Home Vulnerability Monitoring
- Cybersecurity Learning Platforms: HTB, THM, Others
Conclusion
Securing SSH access is not optional—it’s essential for protecting your Linux servers. By following this guide:
- ✅ Use SSH keys instead of passwords
- ✅ Harden SSH configuration with secure settings
- ✅ Change default SSH port
- ✅ Implement Fail2Ban for automatic blocking
- ✅ Enable two-factor authentication
- ✅ Restrict access with IP whitelisting
- ✅ Monitor access logs regularly
- ✅ Use bastion hosts for production
- ✅ Perform regular security audits
- ✅ 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.