Secure Shell (SSH) is the de facto protocol for remote access to Linux servers, enabling secure command-line login, file transfer, and network administration. By default, SSH is relatively secure, but misconfigurations or outdated practices can expose systems to brute-force attacks, credential theft, or unauthorized access. This tutorial will guide you through fundamental concepts, practical steps, and best practices to harden SSH on Linux, ensuring your remote access remains protected against common threats.
Table of Contents
- Understanding SSH Basics
- Prerequisites
- Step 1: Update SSH and Dependencies
- Step 2: Configure the SSH Daemon (
sshd_config) - Step 3: Enable Key-Based Authentication
- Step 4: Disable Password Authentication
- Step 5: Secure SSH with a Firewall
- Step 6: Mitigate Brute-Force Attacks with Fail2ban
- Step 7: Enforce Two-Factor Authentication (2FA)
- Step 8: Limit User and Group Access
- Step 9: Enhance Logging and Monitoring
- Step 10: Regular Audits and Updates
- Common Practices
- Best Practices
- Conclusion
- References
1. Understanding SSH Basics
SSH (Secure Shell) is a cryptographic network protocol that replaces insecure tools like Telnet and FTP by encrypting data in transit. It uses a client-server model:
- SSH Client: Runs on your local machine (e.g.,
sshcommand-line tool, PuTTY). - SSH Server: Runs on the remote Linux server (typically
sshd, the OpenSSH daemon).
Core Risks Without Hardening:
- Brute-force attacks (automated tools guessing passwords).
- Compromised credentials (e.g., weak passwords).
- Unpatched vulnerabilities in SSH software.
- Misconfigurations (e.g., allowing root login, password-only auth).
2. Prerequisites
Before starting, ensure:
- A Linux server running OpenSSH (most distributions include it by default).
- Sudo or root access to the server.
- A backup of critical data (in case of misconfiguration).
- A secondary terminal session open (to recover if SSH access is lost during setup).
3. Step 1: Update SSH and Dependencies
Outdated SSH software may contain vulnerabilities (e.g., CVE-2024-6387). Always start by updating openssh-server and dependencies:
# For Debian/Ubuntu
sudo apt update && sudo apt upgrade openssh-server -y
# For RHEL/CentOS/Rocky Linux
sudo dnf update openssh-server -y
# Verify the installed version
ssh -V # Should return "OpenSSH_<version>"
4. Step 2: Configure the SSH Daemon (sshd_config)
The sshd_config file (usually at /etc/ssh/sshd_config) controls SSH server behavior. Always back up the file first:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
Edit the file with a text editor (e.g., nano or vim):
sudo nano /etc/ssh/sshd_config
Key Configurations to Update:
| Directive | Recommended Value | Purpose |
|---|---|---|
Port | Non-default (e.g., 2222) | Avoids automated scans targeting port 22. |
PermitRootLogin | no | Prevents direct root login (use sudo after user login instead). |
MaxAuthTries | 3 | Limits failed login attempts per connection (blocks brute-force tools). |
LoginGraceTime | 30s | Reduces time window for brute-force attempts (default: 2m). |
StrictModes | yes | Ensures proper permissions for user home/SSH files (prevents misconfig). |
Example sshd_config Snippet:
Port 2222 # Change from default 22
PermitRootLogin no
MaxAuthTries 3
LoginGraceTime 30s
StrictModes yes
After editing, validate the configuration to avoid syntax errors:
sudo sshd -t # Returns "OK" if valid
4. Step 3: Enable Key-Based Authentication
Passwords are vulnerable to brute-force attacks. Key-based authentication uses cryptographic key pairs (public/private) for login, which is far more secure.
Step 3.1: Generate a Key Pair (Client-Side)
On your local machine, generate an SSH key pair. Use the modern ed25519 algorithm (more secure and faster than RSA):
ssh-keygen -t ed25519 -C "[email protected]"
-t ed25519: Specifies the Ed25519 algorithm.-C: Adds a comment (e.g., email) to identify the key.
When prompted, set a strong passphrase (encrypts the private key, adding a second layer of security).
Step 3.2: Copy the Public Key to the Server
Transfer your public key to the remote server’s ~/.ssh/authorized_keys file. Use ssh-copy-id (replace user and server_ip with your details):
# If using default port 22:
ssh-copy-id user@server_ip
# If using a custom port (e.g., 2222):
ssh-copy-id -p 2222 user@server_ip
Alternatively, manually copy the public key (e.g., if ssh-copy-id is unavailable):
# On local machine:
cat ~/.ssh/id_ed25519.pub # Copy the output
# On the server (via existing SSH session):
mkdir -p ~/.ssh && chmod 700 ~/.ssh
nano ~/.ssh/authorized_keys # Paste the public key here
chmod 600 ~/.ssh/authorized_keys # Restrict permissions
Step 3.3: Test Key-Based Login
Verify key-based login works before disabling passwords:
# If using a custom port:
ssh -p 2222 user@server_ip
You should log in without a password (but you’ll need your key passphrase if set).
5. Step 4: Disable Password Authentication
Once key-based login is confirmed, disable password authentication on the server to block password-based attacks.
Edit sshd_config again:
sudo nano /etc/ssh/sshd_config
Set:
PasswordAuthentication no
ChallengeResponseAuthentication no # Disables keyboard-interactive auth
Restart the SSH daemon to apply changes:
# Debian/Ubuntu:
sudo systemctl restart sshd
# RHEL/CentOS/Rocky Linux:
sudo systemctl restart sshd
6. Step 5: Secure SSH with a Firewall
A firewall limits SSH access to trusted IPs and blocks unauthorized traffic. Use ufw (Uncomplicated Firewall) or firewalld (default on RHEL-based systems).
Example with ufw (Debian/Ubuntu):
# Allow SSH on your custom port (e.g., 2222)
sudo ufw allow 2222/tcp
# Allow only specific IPs (e.g., your home network: 192.168.1.0/24)
sudo ufw allow from 192.168.1.0/24 to any port 2222/tcp
# Enable the firewall
sudo ufw enable
# Verify rules
sudo ufw status
Example with firewalld (RHEL/CentOS):
# Add SSH port and permanentize the rule
sudo firewall-cmd --add-port=2222/tcp --permanent
sudo firewall-cmd --reload
# Verify
sudo firewall-cmd --list-ports
7. Step 6: Mitigate Brute-Force Attacks with Fail2ban
Fail2ban monitors log files for repeated failed login attempts and temporarily bans malicious IPs.
Step 6.1: Install Fail2ban
# Debian/Ubuntu
sudo apt install fail2ban -y
# RHEL/CentOS/Rocky Linux
sudo dnf install fail2ban -y
Step 6.2: Configure Fail2ban
Create a custom configuration file (avoids overwriting defaults during updates):
sudo nano /etc/fail2ban/jail.local
Add SSH protection:
[sshd]
enabled = true
port = 2222 # Use your custom SSH port
filter = sshd
logpath = /var/log/auth.log # Debian/Ubuntu; RHEL uses /var/log/secure
maxretry = 3 # Ban after 3 failed attempts
bantime = 3600 # Ban duration (1 hour in seconds)
ignoreip = 192.168.1.0/24 # Whitelist trusted IPs (e.g., home network)
Step 6.3: Restart Fail2ban
sudo systemctl restart fail2ban
sudo systemctl enable fail2ban # Start on boot
Check banned IPs with:
sudo fail2ban-client status sshd
8. Step 7: Enforce Two-Factor Authentication (2FA)
Add an extra layer of security with 2FA, requiring both a private key and a one-time code (e.g., from Google Authenticator).
Step 7.1: Install Google Authenticator PAM Module
# Debian/Ubuntu
sudo apt install libpam-google-authenticator -y
# RHEL/CentOS/Rocky Linux
sudo dnf install google-authenticator -y
Step 7.2: Configure 2FA for a User
Run the authenticator tool as the user who will use 2FA:
google-authenticator
Answer the prompts:
Do you want authentication tokens to be time-based? (y/n):y(TOTP standard).- Scan the QR code with your Google Authenticator app (or enter the secret key manually).
- Save the emergency scratch codes (for account recovery if you lose your device).
Do you want me to update your "/home/user/.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 enable rate-limiting? (y/n):y(limits 3 logins per 30 seconds).
Step 7.3: Update SSH to Use PAM for 2FA
Edit sshd_config to enable PAM (Pluggable Authentication Module) and challenge-response auth:
sudo nano /etc/ssh/sshd_config
Set:
ChallengeResponseAuthentication yes # Re-enable for 2FA
UsePAM yes
Restart sshd:
sudo systemctl restart sshd
Now, SSH login will require:
- Your private key (key-based auth).
- A 6-digit code from Google Authenticator (2FA).
9. Step 8: Limit User and Group Access
Restrict SSH access to specific users or groups to minimize exposure.
Edit sshd_config:
sudo nano /etc/ssh/sshd_config
Allow Only Specific Users
Use AllowUsers to whitelist users (separate with spaces):
AllowUsers [email protected]/24 bob # Alice can connect from 192.168.1.0/24; Bob from anywhere
Allow Only Specific Groups
Create a group (e.g., ssh-users) and allow only its members:
sudo groupadd ssh-users
sudo usermod -aG ssh-users alice # Add user "alice" to the group
In sshd_config:
AllowGroups ssh-users
Restart sshd to apply:
sudo systemctl restart sshd
10. Step 9: Enhance Logging and Monitoring
SSH logs are critical for detecting attacks. Configure sshd to log detailed activity and monitor logs regularly.
Step 9.1: Enable Verbose Logging
In sshd_config, set LogLevel to VERBOSE (logs key exchanges, auth attempts, etc.):
LogLevel VERBOSE
Step 9.2: View SSH Logs
Logs are stored in:
- Debian/Ubuntu:
/var/log/auth.log - RHEL/CentOS:
/var/log/secure
Use tail or grep to monitor live activity:
sudo tail -f /var/log/auth.log | grep sshd # "tail -f" follows real-time logs
Step 9.3: Alert on Suspicious Activity
Use tools like logwatch or fail2ban to send email alerts for failed logins:
# Install logwatch (Debian/Ubuntu)
sudo apt install logwatch -y
# Configure daily email reports (edit /etc/logwatch/conf/logwatch.conf)
sudo nano /etc/logwatch/conf/logwatch.conf
Set MailTo = [email protected] to receive daily summaries.
11. Step 10: Regular Audits and Updates
SSH security is not “set and forget.” Regularly audit configurations and update software to patch vulnerabilities.
Audit Tools:
ssh-audit: Scans SSH server configuration for weaknesses (GitHub).# Run from local machine (replace port and IP) ssh-audit server_ip:2222sshd -T: Lists all activesshdconfiguration parameters (compare with hardening guidelines).
Update SSH Software:
Run regular system updates to patch openssh-server:
# Debian/Ubuntu
sudo apt update && sudo apt upgrade -y
# RHEL/CentOS/Rocky Linux
sudo dnf update -y
12. Common Practices
- Avoid default port 22: Use a non-standard port (e.g., 2222) to reduce scan exposure.
- Never hardcode keys: Store private keys securely (e.g.,
~/.ssh/withchmod 600permissions). - Revoke compromised keys: Remove unauthorized public keys from
~/.ssh/authorized_keys. - Use SSH agents: Tools like
ssh-agentorkeychainmanage passphrases, avoiding repeated entry.
13. Best Practices
- Use Ed25519 keys: Prefer over RSA for stronger security and better performance.
- Enforce MFA for all users: Even key-based auth benefits from 2FA as a backup.
- Limit SSH to trusted IPs: Use firewalls to block access from untrusted networks.
- Rotate keys annually: Expire old keys and generate new ones to minimize exposure.
- Monitor logs with SIEM tools: Use solutions like ELK Stack or Splunk to detect anomalies at scale.
14. Conclusion
Securing SSH is critical for protecting remote server access. By following this tutorial, you’ve implemented key hardening steps: non-default ports, key-based auth, 2FA, firewall rules, and logging. Remember, security is iterative—regularly audit configurations, update software, and stay informed about new threats (e.g., via CVE databases). With these measures, you’ll significantly reduce the risk of SSH-related breaches.