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.

Raymond Ops
Raymond Ops
Raymond Ops
Frequent SSH Brute‑Force Attacks? Essential Defense Measures You Must Configure

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 -20

1.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 -20

1.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 -20

1.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, nagios

Step 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_keys

2.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 yes

2.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 fail2ban

3.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 = 7200

3.4 Enable and start Fail2Ban

sudo systemctl start fail2ban
sudo systemctl enable fail2ban
sudo systemctl status fail2ban

3.5 Verify Fail2Ban operation

sudo fail2ban-client status sshd
# Example output shows currently banned IPs, total failures, etc.
sudo iptables -L f2b-sshd -n

3.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 hours

Step 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., 2222

4.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 :22

Step 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/iptables

5.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-rules

5.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 numbered

Step 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-libpam

6.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 nullok

6.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 sshd

6.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-hids

7.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 -20

Conclusion

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

linux-securitytwo-factor-authenticationsshintrusion-detectionbrute-forcepublic-key-authenticationfail2banport-hiding
Raymond Ops
Written by

Raymond Ops

Linux ops automation, cloud-native, Kubernetes, SRE, DevOps, Python, Golang and related tech discussions.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.