⚖️ Educational Defense Notice: This guide explains real-world automated brute-force attacks against SSH servers and shows how to set up active intrusion prevention. Practicing brute-force credential stuffing against systems you do not own violates the Computer Fraud and Abuse Act and international cybersecurity laws.

The moment you spin up a brand new Linux server on AWS, DigitalOcean, Hetzner, or Linode, a timer starts ticking. Within ten to fifteen minutes of your public IP address appearing on the internet, automated botnets will find your machine and begin attempting to log in.

They do not need to know who you are. Massive global scanning networks constantly sweep the entire IPv4 address space, probing TCP port 22. When they find an open SSH listener, an automated dictionary attack kicks off: testing hundreds of common usernames and passwords every minute.

In this guide, we look at the raw logs of an active SSH brute-force attack, examine why weak passwords fail against dictionary scripts, and configure Fail2ban to automatically detect and block malicious IP addresses before they guess correctly.

1. The Raw Firehose: What an SSH Attack Looks Like

Every SSH connection attempt is recorded by your Linux operating system. On Debian and Ubuntu servers, SSH logs to /var/log/auth.log. On RHEL, Rocky, and Fedora systems, it logs to /var/log/secure or systemd journal.

If you run a live stream on your authentication log on any fresh cloud server, this is what you will witness:

bash - live stream of incoming authentication attempts
$ sudo tail -f /var/log/auth.log | grep "Failed password"

Nov 10 03:14:12 server sshd[18420]: Failed password for invalid user admin from 185.220.101.5 port 41232 ssh2
Nov 10 03:14:15 server sshd[18422]: Failed password for invalid user ubuntu from 185.220.101.5 port 41238 ssh2
Nov 10 03:14:18 server sshd[18425]: Failed password for root from 185.220.101.5 port 41244 ssh2
Nov 10 03:14:21 server sshd[18429]: Failed password for invalid user test from 185.220.101.5 port 41250 ssh2
Nov 10 03:14:24 server sshd[18433]: Failed password for invalid user oracle from 185.220.101.5 port 41256 ssh2

Notice the cadence: every three seconds, the same remote IP address (185.220.101.5) attempts a new credential pair. The script cycles through common default system accounts: admin, ubuntu, root, test, oracle, git, and postgres.

If your server allows password authentication and has a common password like password123 or admin2026, a dictionary attack will eventually succeed. Even if your password is strong, hundreds of thousands of failed attempts waste server memory, flood log files, and consume CPU cycles.

2. Enter Fail2ban: The Automated Digital Bouncer

A firewall like UFW or iptables is static: it either opens port 22 to everyone or blocks port 22 completely. If you block port 22, you cannot administer your server remotely. If you open port 22, botnets hammer it.

Fail2ban bridges this gap by making your firewall dynamic:

  1. It monitors system log files in real time (using Python regex filters).
  2. It counts failed authentication attempts from each unique IP address within a configurable time window.
  3. When an IP exceeds the threshold (e.g. 3 failed logins within 10 minutes), Fail2ban dynamically injects a temporary drop rule into your firewall (iptables or nftables).
  4. Any further packets from that attacking IP are silently discarded before they even reach the SSH daemon.
  5. Once the ban duration expires (e.g. 24 hours), Fail2ban removes the firewall rule automatically.
flow - how fail2ban operates in memory
[Attacker IP: 185.220.101.5]
    |
    v
[Port 22 SSH Attempt 1] -> Fails -> auth.log (Fail count: 1)
[Port 22 SSH Attempt 2] -> Fails -> auth.log (Fail count: 2)
[Port 22 SSH Attempt 3] -> Fails -> auth.log (Fail count: 3 -> THRESHOLD REACHED!)
    |
    v
[Fail2ban Action Triggered]
    |--> iptables -I INPUT -s 185.220.101.5 -j DROP
    |
    v
[Attempt 4+] -> Packets dropped at kernel level. Zero CPU/SSH overhead.

3. Installing and Configuring Fail2ban on Ubuntu / Debian

Setting up Fail2ban takes less than three minutes. First, update your package repository and install the daemon:

bash - installing fail2ban
sudo apt update
sudo apt install -y fail2ban

The Golden Rule: Never Edit jail.conf Directly

Fail2ban ships with a default configuration file located at /etc/fail2ban/jail.conf. Whenever the package updates through apt upgrade, this file can get overwritten.

Always create a local override file called jail.local. Any setting you specify in jail.local takes precedence over jail.conf:

bash - create custom jail configuration
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local

4. Recommended Production Jail Settings

Inside /etc/fail2ban/jail.local, find the [DEFAULT] and [sshd] sections. Configure these essential parameters:

/etc/fail2ban/jail.local - hardened ssh jail settings
[DEFAULT]
# Never ban your own home or office IP address (whitelist)
ignoreip = 127.0.0.1/8 ::1 203.0.113.45

# How long the attacker is banned (e.g. 1d = 24 hours, 1h = 1 hour)
bantime = 1d

# Time window in which failed attempts are tracked
findtime = 10m

# Number of failed attempts before ban triggers
maxretry = 3

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 1d
💡 Always Whitelist Your Own IP:
Make sure to add your own public IP address to the ignoreip directive. If you accidentally misplace an SSH key or mistype a passphrase three times, you do not want your own server locking you out for 24 hours.

Save the file and restart the Fail2ban service to activate your new rules:

bash - restarting fail2ban and verifying status
sudo systemctl restart fail2ban
sudo systemctl enable fail2ban
sudo fail2ban-client status

5. Watching Fail2ban Jail Attackers in Real Time

Once Fail2ban is running, you can monitor active jails using the fail2ban-client command-line utility. Check the specific status of the SSH jail:

bash - inspecting currently jailed hacker ips
$ sudo fail2ban-client status sshd

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     418
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 3
   |- Total banned:     37
   `- Banned IP list:   185.220.101.5 194.26.29.112 45.154.255.89

In this output, Fail2ban detected 418 total failed login attempts, identified repeat offenders, and actively jailed 3 IP addresses right now. Those machines are completely blocked at the kernel network layer.

How to Manually Unban an IP Address

If a colleague or friend gets accidentally banned after entering the wrong password, you can unban their IP address immediately from your terminal:

bash - unban a specific ip address
sudo fail2ban-client set sshd unbanip 185.220.101.5

6. The 3 Ultimate Rules for Bulletproof SSH Defense

Fail2ban is your active line of defense, but true server hardening requires combining it with these three fundamental habits:

Summary Checklist

Setting up Fail2ban gives your Linux servers automated resilience against internet noise. Once configured, you can sleep soundly knowing brute-force bots get stopped in their tracks.