Fail2Ban: Protect Your Linux Server from Brute Force Attacks

Fail2Ban: Protect Your Linux Server from Brute Force Attacks


If you run a Linux server exposed to the internet, your SSH logs are full of brute force login attempts. Bots constantly hammer port 22, trying thousands of username/password combinations. Fail2Ban is a lightweight intrusion prevention tool that monitors your log files and automatically bans IPs that show malicious behavior — like too many failed login attempts.

In this guide, you’ll learn how to install, configure, and fine-tune Fail2Ban to protect SSH, Nginx, and other services on Ubuntu/Debian and CentOS/RHEL servers.

What Is Fail2Ban and How Does It Work?

Fail2Ban is an open-source log-parsing framework written in Python. It works by:

  1. Monitoring log files — It watches logs like /var/log/auth.log (SSH), /var/log/nginx/error.log (Nginx), and others for patterns that indicate failed authentication or abuse.
  2. Matching patterns with filters — It uses regex-based filter rules to identify failed login attempts, 404 floods, or other suspicious activity.
  3. Banning offending IPs — When an IP exceeds a configured number of failures within a time window, Fail2Ban creates a firewall rule (via iptables, nftables, or UFW) to block that IP for a specified duration.
  4. Unbanning automatically — After the ban time expires, the IP is automatically unbanned.

This approach is effective against automated brute force attacks, credential stuffing, and basic DDoS patterns.

Installing Fail2Ban

Ubuntu / Debian

sudo apt update
sudo apt install fail2ban -y

CentOS / RHEL / Fedora

# CentOS/RHEL 8+ with EPEL
sudo dnf install epel-release -y
sudo dnf install fail2ban -y

Start and Enable the Service

sudo systemctl start fail2ban
sudo systemctl enable fail2ban

Verify it’s running:

sudo systemctl status fail2ban

You should see active (running) in the output.

Understanding the Configuration Structure

Fail2Ban’s configuration lives in /etc/fail2ban/. The key files are:

FilePurpose
jail.confDefault jail definitions (do NOT edit directly)
jail.localYour custom overrides (create this file)
jail.d/Directory for additional jail config files
filter.d/Regex filter definitions for each service
action.d/Action definitions (how to ban/unban IPs)

Important: Never edit jail.conf directly — it gets overwritten on updates. Always create a jail.local file or add files to jail.d/ for your customizations.

Configuring Fail2Ban for SSH

Create your local configuration file:

sudo nano /etc/fail2ban/jail.local

Add the following configuration:

[DEFAULT]
# Ban IP for 1 hour (3600 seconds)
bantime = 3600

# Time window to count failures (10 minutes)
findtime = 600

# Number of failures before banning
maxretry = 5

# Email notifications (optional)
# destemail = admin@yourdomain.com
# sendername = Fail2Ban
# action = %(action_mwl)s

# Ignore your own IPs (whitelist)
ignoreip = 127.0.0.1/8 ::1

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600

What Each Setting Does

  • bantime — How long an IP stays banned (in seconds). Use -1 for permanent bans.
  • findtime — The time window in which maxretry failures must occur to trigger a ban.
  • maxretry — Number of failed attempts allowed before banning.
  • ignoreip — IP addresses or CIDR ranges that should never be banned (always include your own IP here).
  • logpath — The log file Fail2Ban monitors for this jail.
  • filter — The name of the filter in /etc/fail2ban/filter.d/ to use.

Apply the Configuration

sudo systemctl restart fail2ban

Checking Fail2Ban Status

Overall Status

sudo fail2ban-client status

Output:

Status
|- Number of jail:      1
`- Jail list:   sshd

SSH Jail Status

sudo fail2ban-client status sshd

Output:

Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     147
|  `- Journal matches:  _SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned: 3
   |- Total banned:     28
   `- Banned IP list:   203.0.113.42 198.51.100.17 192.0.2.88

This tells you how many IPs are currently banned and the total number of bans since the service started.

Useful Fail2Ban Commands

Here’s a reference of the most common commands:

# Check overall status
sudo fail2ban-client status

# Check a specific jail
sudo fail2ban-client status sshd

# Manually ban an IP
sudo fail2ban-client set sshd banip 203.0.113.42

# Manually unban an IP
sudo fail2ban-client set sshd unbanip 203.0.113.42

# Check which jails are active
sudo fail2ban-client status | grep "Jail list"

# View Fail2Ban's own log
sudo tail -f /var/log/fail2ban.log

# Test a filter regex against a log file
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

# Reload configuration without restarting
sudo fail2ban-client reload

Protecting Nginx with Fail2Ban

Fail2Ban isn’t just for SSH. You can protect web servers too. Here are some useful Nginx jails.

Block Repeated 401/403 Authentication Failures

Add to your /etc/fail2ban/jail.local:

[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600
findtime = 600

Block Bots Scanning for Vulnerabilities

Create a custom filter to catch bots probing for common exploit paths like /wp-admin, /phpmyadmin, /.env, etc.

Create the filter file:

sudo nano /etc/fail2ban/filter.d/nginx-badbots.conf
[Definition]
failregex = ^<HOST> -.*"(GET|POST).*(\.php|\.asp|\.env|wp-login|wp-admin|phpmyadmin|\.git|cgi-bin).*" (404|403|400)
ignoreregex =

Add the jail:

[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 3
bantime = 86400
findtime = 600

This bans any IP that triggers 3 or more 404/403 hits on common exploit paths within 10 minutes, banning them for 24 hours.

Block Aggressive Request Flooding

To catch basic HTTP flood / DDoS patterns:

sudo nano /etc/fail2ban/filter.d/nginx-limit-req.conf
[Definition]
failregex = limiting requests, excess:.* by zone.*client: <HOST>
ignoreregex =

Add the jail:

[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
bantime = 7200
findtime = 600

This works with Nginx’s built-in limit_req_zone rate limiting module. When Nginx logs rate limit violations, Fail2Ban picks them up and bans repeat offenders.

Advanced Configuration

Incremental Ban Times (Recidive Jail)

For repeat offenders who keep coming back after their ban expires, use the recidive jail. This monitors Fail2Ban’s own log and applies longer bans to IPs that get banned multiple times:

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 604800
findtime = 86400
maxretry = 3

This means: if an IP gets banned 3 times across any jail within 24 hours, it gets blocked on all ports for 7 days.

Permanent Bans

To permanently ban an IP after it’s been caught:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = -1

Setting bantime = -1 means the ban never expires. Be careful with this — make sure your own IP is in ignoreip.

Using UFW Instead of iptables

If you’re using UFW (as covered in our UFW Firewall Guide), you can tell Fail2Ban to use UFW for banning:

[DEFAULT]
banaction = ufw
banaction_allports = ufw

This makes Fail2Ban insert UFW rules instead of raw iptables rules, keeping your firewall management consistent.

Using nftables

On modern systems using nftables:

[DEFAULT]
banaction = nftables-multiport
banaction_allports = nftables-allports

Email Notifications

To receive email alerts when IPs are banned, configure the [DEFAULT] section:

[DEFAULT]
destemail = admin@yourdomain.com
sendername = Fail2Ban
mta = sendmail

# Action with email + whois + log lines
action = %(action_mwl)s

The action types are:

  • action_ — Just ban the IP (default)
  • action_mw — Ban + send email with whois info
  • action_mwl — Ban + send email with whois info + relevant log lines

You’ll need sendmail or msmtp installed and configured for email to work.

Testing Your Configuration

Test a Filter Against a Log

Before enabling a new jail, test its regex against your actual log files:

sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

Output shows how many matches were found:

Running tests

Use   failregex filter file : sshd, basedir: /etc/fail2ban
Use         log file : /var/log/auth.log
Use         encoding : UTF-8

Results

Failregex: 147 total
|-  #) [# of hits] regular expression
|   1) [147] ^.*Failed password for .* from <HOST>.*$
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [2634] {^LN-BEG}(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
`-

Lines: 2634 lines, 0 ignored, 147 matched, 2487 missed

If the matched count looks reasonable, your filter is working correctly.

Simulate a Ban (Dry Run)

You can test what would happen without actually banning anyone by checking the log manually:

# Watch the fail2ban log in real-time
sudo tail -f /var/log/fail2ban.log

# In another terminal, try to trigger a ban (e.g., failed SSH logins)
# WARNING: Only do this on your own test server
ssh invalid_user@localhost

Monitoring and Logging

View Recent Bans

# Show recent ban/unban activity
sudo grep "Ban\|Unban" /var/log/fail2ban.log | tail -20

Count Bans Per IP

# Find the most frequently banned IPs
sudo awk '/Ban/ {print $NF}' /var/log/fail2ban.log | sort | uniq -c | sort -rn | head -20

Log Rotation

Fail2Ban’s log at /var/log/fail2ban.log is automatically rotated by logrotate on most systems. You can check the configuration at /etc/logrotate.d/fail2ban.

Complete Example Configuration

Here’s a production-ready /etc/fail2ban/jail.local that protects SSH, Nginx, and handles repeat offenders:

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 YOUR_HOME_IP
banaction = iptables-multiport
banaction_allports = iptables-allports

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 7200

[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600

[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = /var/log/nginx/access.log
maxretry = 3
bantime = 86400

[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 10
bantime = 7200

[recidive]
enabled = true
logpath = /var/log/fail2ban.log
banaction = %(banaction_allports)s
bantime = 604800
findtime = 86400
maxretry = 3

Replace YOUR_HOME_IP with your actual static IP address to avoid locking yourself out.

Troubleshooting

Fail2Ban Won’t Start

# Check for configuration errors
sudo fail2ban-client -t

# Check the system journal for errors
sudo journalctl -u fail2ban -n 50

IPs Not Getting Banned

  1. Check the log path — Make sure logpath in your jail points to the correct log file.
  2. Test the filter — Run fail2ban-regex to verify the filter matches your log format.
  3. Check if the jail is enabled — Run sudo fail2ban-client status to see active jails.
  4. Check timestamps — Fail2Ban ignores log entries with timestamps older than findtime.

Locked Yourself Out

If you accidentally banned your own IP:

  1. Access the server via console (cloud provider’s web console, IPMI, or physical access).
  2. Unban your IP:
sudo fail2ban-client set sshd unbanip YOUR_IP_ADDRESS
  1. Add your IP to ignoreip in jail.local to prevent this in the future.

Fail2Ban vs. Other Solutions

FeatureFail2BanCrowdSecSSHGuard
LanguagePythonGoC
Log ParsingRegexYAML scenariosBuilt-in patterns
Community BlocklistsNoYesNo
Services SupportedManyManySSH-focused
Resource UsageLowMediumVery Low
ConfigurationINI filesYAMLSimple config

Fail2Ban remains the most widely deployed solution due to its flexibility, extensive filter library, and straightforward configuration. For most self-hosted servers, it’s the best choice.

Summary

Fail2Ban is an essential tool for any internet-facing Linux server. Here’s what you should take away:

  • Always protect SSH — Enable the sshd jail with a low maxretry (3 attempts is reasonable).
  • Use jail.local — Never edit jail.conf directly.
  • Whitelist your IP — Add your static IP to ignoreip to avoid locking yourself out.
  • Combine with other security measures — Fail2Ban works best alongside SSH key authentication, UFW/firewall rules, and disabling root login.
  • Monitor the logs — Check fail2ban-client status periodically to see what’s being blocked.
  • Use the recidive jail — Escalate bans for persistent attackers.

Combined with SSH key authentication and a properly configured UFW firewall, Fail2Ban adds a strong automated defense layer that keeps your server secure with minimal ongoing maintenance.