Frequent SSH Brute‑Force Attacks? Essential Defense Measures You Must Configure
When a server is exposed to the Internet, SSH brute‑force attempts are inevitable; this guide walks Linux operators through log analysis, disabling password authentication, enabling public‑key and 2FA, configuring fail2ban, changing the default port, restricting source IPs, deploying OSSEC, and automating daily and weekly security checks.
Problem Background
Any server reachable from the public Internet will inevitably see SSH port scans and brute‑force login attempts. In the author’s test environment, the first day of deployment generated alerts for attempts from worldwide IPs targeting accounts such as root, admin, test, and oracle. Weak passwords make successful compromises easy, allowing attackers to install backdoors, cryptocurrency miners, or pivot to internal systems.
Step 1 – Know Your Enemy: Analyze Attack Logs
1.1 View SSH login failures
# CentOS/RHEL
sudo tail -500 /var/log/secure | grep -i "failed password"
# Ubuntu/Debian
sudo tail -500 /var/log/auth.log | grep -i "failed password"1.2 Count attacking IPs and attempts
# CentOS
sudo grep "Failed password" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -rn | head -20
# Ubuntu
sudo grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head -201.3 Identify targeted usernames
# CentOS
sudo grep "Failed password" /var/log/secure | awk '{print $9}' | sort | uniq -c | sort -rn | head -20
# Ubuntu
sudo grep "Failed password" /var/log/auth.log | awk '{print $9}' | sort | uniq -c | sort -rn | head -201.4 Detect successful logins
# CentOS
sudo grep "Accepted" /var/log/secure | awk '{print $9, $11, $13}'
# Ubuntu
sudo grep "Accepted" /var/log/auth.log | awk '{print $9, $11, $13}'
sudo last | head -201.5 Common usernames in attack dictionaries
root, admin, user, test, guest, oracle, mysql, postgres, ubuntu, centos, debian, www-data, apache, nginx, tomcat, redis, mongodb, backup, ftp, nagiosStep 2 – The Most Thorough Defense: Disable Password Login and Use Public‑Key Authentication
2.1 Why passwords are weak
Passwords can be guessed or brute‑forced.
Keyloggers and phishing can steal passwords.
Common weak passwords (e.g., 123456, password) are cracked instantly.
2.2 Advantages of public‑key authentication
Based on asymmetric cryptography, resistant to brute‑force.
Private keys are usually protected by a passphrase.
Without the private key, login is impossible.
2.3 Generate an SSH key pair
# On the local workstation
ssh-keygen -t ed25519 -C "[email protected]"
# If the server is very old and does not support ed25519
ssh-keygen -t rsa -b 4096 -C "[email protected]"2.4 Upload the public key to the server
# Method 1 – simplest
ssh-copy-id -i ~/.ssh/id_ed25519.pub admin@<server-ip>
# Method 2 – manual copy
cat ~/.ssh/id_ed25519.pub # copy the output
ssh admin@<server-ip>
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "<paste your public key here>" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys2.5 Disable password authentication in sshd_config
sudo vi /etc/ssh/sshd_config
# Ensure the following lines are present and uncommented
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM yes2.6 Restart SSH and verify
sudo systemctl restart sshd
# Test that password login is rejected
ssh admin@<server-ip>
# Test that key login works
ssh -i ~/.ssh/id_ed25519 admin@<server-ip>Step 3 – Fail2Ban: Automatic IP Banning
3.1 How Fail2Ban works
Fail2Ban monitors log files, and when an IP generates multiple failed login attempts within a short window, it automatically adds a firewall rule to block that IP for a configurable duration.
3.2 Install Fail2Ban
# CentOS/RHEL (EPEL required)
sudo yum install -y epel-release
sudo yum install -y fail2ban
# Ubuntu/Debian
sudo apt-get install -y fail2ban3.3 Configure Fail2Ban
# /etc/fail2ban/jail.local (user‑defined, overrides jail.conf)
[DEFAULT]
bantime = 3600 ; 1 hour
findtime = 600 ; 10 minutes
maxretry = 5
banaction = iptables-multiport
ignoreip = 127.0.0.1/8 ::1 192.168.1.100 10.0.0.50
[sshd]
enabled = true
port = ssh
logpath = /var/log/secure ; CentOS
#logpath = /var/log/auth.log ; Ubuntu
filter = sshd
maxretry = 3
bantime = 72003.4 Enable and start Fail2Ban
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
sudo systemctl status fail2ban3.5 Verify Fail2Ban operation
sudo fail2ban-client status sshd
# Example output shows currently banned IPs, total failures, etc.
sudo iptables -L f2b-sshd -n3.6 Advanced configuration (email alerts, stricter thresholds)
# In jail.local add:
destemail = [email protected]
sender = [email protected]
action = %(action_mwl)s ; ban + send email with log excerpt
# Stricter rule example
findtime = 300 ; 5 minutes
maxretry = 2
bantime = 7200 ; 2 hoursStep 4 – Change the Default SSH Port
4.1 Modify sshd_config
sudo vi /etc/ssh/sshd_config
Port 2222 # any non‑standard port, e.g., 22224.2 Adjust firewall and verify
# CentOS (firewalld)
sudo firewall-cmd --permanent --add-port=2222/tcp
sudo firewall-cmd --reload
# Ubuntu (ufw)
sudo ufw allow 2222/tcp
sudo systemctl restart sshd
ssh -p 2222 admin@<server-ip>
# Confirm the old port 22 is no longer listening
sudo ss -tunapl | grep :22Step 5 – Restrict SSH Source IPs
5.1 iptables example
# Flush existing SSH rules (optional)
sudo iptables -D INPUT -p tcp --dport 22 -j ACCEPT
# Allow only trusted IPs (example)
sudo iptables -A INPUT -p tcp -s 192.168.1.100 --dport 2222 -j ACCEPT
sudo iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 2222 -j ACCEPT
sudo iptables -A INPUT -p tcp -s 10.0.0.0/8 --dport 2222 -j ACCEPT
sudo iptables-save > /etc/sysconfig/iptables5.2 firewalld rich‑rule example
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.100" port port="2222" protocol="tcp" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" port port="2222" protocol="tcp" accept'
sudo firewall-cmd --reload
sudo firewall-cmd --list-rich-rules5.3 ufw example
sudo ufw allow from 192.168.1.100 to any port 2222
sudo ufw allow from 192.168.1.0/24 to any port 2222
sudo ufw status numberedStep 6 – Two‑Factor Authentication (2FA)
6.1 Install Google Authenticator PAM module
# CentOS/RHEL
sudo yum install -y google-authenticator
# Ubuntu/Debian
sudo apt-get install -y libpam-google-authenticator
# macOS (client side only)
brew install google-authenticator-libpam6.2 Configure a user for 2FA
su - admin
google-authenticator # follow interactive prompts, enable time‑based tokens, disallow token reuse, etc.6.3 Enable PAM for SSH
sudo vi /etc/pam.d/sshd
# Add before the existing auth line:
auth required pam_google_authenticator.so nullok6.4 Enable challenge‑response in sshd_config
sudo vi /etc/ssh/sshd_config
ChallengeResponseAuthentication yes
PubkeyAuthentication yes
PasswordAuthentication no # keep disabled
UsePAM yes
# Restart SSH
sudo systemctl restart sshd6.5 Test 2FA login
ssh admin@<server-ip>
# Prompt shows "Verification code:" followed by password prompt.
# Enter the code from Google Authenticator app.Step 7 – Intrusion Detection with OSSEC
7.1 Install OSSEC (HIDS)
# CentOS
sudo yum install -y ossec-hids
# Ubuntu/Debian
sudo apt-get install -y ossec-hids7.2 Typical detection commands
# Look for newly added users
cat /etc/passwd | grep -E "test|hack|backup"
# Check for new authorized keys
cat ~/.ssh/authorized_keys | head -10
# Review recent command history
history
# Examine cron jobs
sudo crontab -l
sudo cat /var/spool/cron/root
# List established network connections
ss -tunapl | grep ESTABLISHED
# Search for suspicious processes
ps aux | grep -iE "xmrig|miner|kinsing|kworkerds|illegal"Step 8 – Comprehensive Hardening Recommendations
8.1 Minimal security profile (suitable for most servers)
Public‑key authentication, password login disabled.
Fail2Ban automatic banning.
Non‑standard SSH port.
Restrict SSH access to specific IP ranges.
8.2 High‑security profile (for critical infrastructure)
Public‑key authentication + 2FA.
Fail2Ban.
Non‑standard port.
IP whitelisting.
OSSEC intrusion detection.
Email alerts on every SSH login.
8.3 Full configuration script (example)
#!/bin/bash
# WARNING: Ensure you have an out‑of‑band console (VNC/ILO) before running.
set -e
SSH_PORT=2222
# Install fail2ban
if command -v yum >/dev/null; then
sudo yum install -y epel-release fail2ban
elif command -v apt-get >/dev/null; then
sudo apt-get install -y fail2ban
fi
# Create jail.local
sudo tee /etc/fail2ban/jail.local >/dev/null <<EOF
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = iptables-multiport
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
port = $SSH_PORT
logpath = /var/log/secure
filter = sshd
maxretry = 3
bantime = 7200
EOF
sudo systemctl enable --now fail2ban
# Backup original sshd_config
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d)
# Harden sshd_config
sudo tee /etc/ssh/sshd_config.d/security.conf >/dev/null <<EOF
Port $SSH_PORT
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication yes
UsePAM yes
X11Forwarding no
AllowTcpForwarding no
PermitRootLogin no
EOF
sudo systemctl restart sshd
# Open new port in firewall (firewalld example)
if command -v firewall-cmd >/dev/null; then
sudo firewall-cmd --permanent --add-port=$SSH_PORT/tcp
sudo firewall-cmd --reload
fi
echo "SSH hardening complete. New port: $SSH_PORT"Step 9 – Ongoing Operational Checklist
9.1 Daily security check script
#!/bin/bash
LOG_FILE="/var/log/ssh_security_check.log"
ALERT_EMAIL="[email protected]"
# Check fail2ban status
FAIL2BAN_STATUS=$(systemctl is-active fail2ban)
if [ "$FAIL2BAN_STATUS" != "active" ]; then
echo "[ALERT] fail2ban not running!" | tee -a $LOG_FILE
fi
# Count recent failed attempts
FAILED_COUNT=$(grep "Failed password" /var/log/secure 2>/dev/null | wc -l)
echo "SSH failed login count: $FAILED_COUNT" >> $LOG_FILE
# Detect unknown successful logins
KNOWN_IPS="192.168.1.100 10.0.0.50"
ACCEPTED_LOGINS=$(grep "Accepted" /var/log/secure 2>/dev/null)
for ip in $KNOWN_IPS; do
ACCEPTED_LOGINS=$(echo "$ACCEPTED_LOGINS" | grep -v "$ip")
done
if [ -n "$ACCEPTED_LOGINS" ]; then
echo "[ALERT] Unknown successful SSH login!" | tee -a $LOG_FILE
echo "$ACCEPTED_LOGINS" | tee -a $LOG_FILE | mail -s "SSH unknown login alert" $ALERT_EMAIL
fi
# Verify sshd_config integrity (compare MD5 with stored value)
# ... (implementation omitted for brevity)9.2 Weekly audit script (summary statistics)
#!/bin/bash
echo "===== SSH Brute‑Force Weekly Report ====="
echo "Period: $(date -d '7 days ago' +%Y-%m-%d) to $(date +%Y-%m-%d)"
# Top attacking IPs
grep "Failed password" /var/log/secure -m 10000 | awk '{print $11}' | sort | uniq -c | sort -rn | head -20
# Top attempted usernames
grep "Failed password" /var/log/secure -m 10000 | awk '{print $9}' | sort | uniq -c | sort -rn | head -20
# Total failures
TOTAL_FAILED=$(grep "Failed password" /var/log/secure -m 10000 | wc -l)
echo "Total failed attempts this week: $TOTAL_FAILED"
# New Fail2Ban bans
BANNED_IPS=$(sudo iptables -L f2b-sshd -n | grep REJECT | awk '{print $4}' | sort | uniq | wc -l)
echo "New Fail2Ban bans this week: $BANNED_IPS"
# Authorized_keys audit
if [ -f /root/.ssh/authorized_keys ]; then
echo "Current authorized_keys count: $(wc -l < /root/.ssh/authorized_keys)"
echo "Last modification: $(stat -c %y /root/.ssh/authorized_keys)"
fi
# New user accounts
lastlog -b 7 | grep -v "Never" | tail -20Conclusion
SSH brute‑force attacks are a persistent threat, but layered defenses—public‑key authentication, Fail2Ban, port obfuscation, IP whitelisting, optional 2FA, and continuous monitoring with OSSEC and scripted audits—drastically raise the attack cost and keep servers secure. Always retain an out‑of‑band access method before applying restrictive firewall rules.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Raymond Ops
Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
