->
+
prometheus
+
grpc
+
+
eslint
+
+
+
+
+
+
<=
+
aurelia
solid
pandas
pytest
+
+
prettier
+
fastapi
ractive
+
soap
++
weaviate
fiber
adonis
+
+
delphi
+
packer
alpine
+
strapi
sklearn
suse
~
bundler
+
!
+
+
+
+
+
+
+
bun
0b
clickhouse
choo
+
cargo
nuxt
*
+
+
riot
+
+
dynamo
π
+
+
+
hack
+
ada
+
jenkins
+
+
+
+
f#
+
+
clion
+
->
+
+
+
Back to Blog
Setting Up Fail2ban for Intrusion Prevention on AlmaLinux
AlmaLinux Security Fail2ban

Setting Up Fail2ban for Intrusion Prevention on AlmaLinux

Published Jul 27, 2025

Protect your AlmaLinux server from brute-force attacks with Fail2ban. Learn installation, configuration, custom jails, filters, actions, and best practices for automated intrusion prevention.

26 min read
0 views
Table of Contents

Fail2ban is an intrusion prevention software framework that protects servers from brute-force attacks by monitoring log files and banning IP addresses that show malicious patterns. This comprehensive guide will walk you through setting up, configuring, and optimizing Fail2ban on AlmaLinux to create a robust automated defense system against various attack vectors.

Understanding Fail2ban

What is Fail2ban?

Fail2ban is a log-parsing application that monitors system logs for suspicious activity and automatically bans IP addresses that exhibit malicious behavior. It works by:

  1. Monitoring log files for failed authentication attempts
  2. Detecting patterns that indicate malicious activity
  3. Taking action by updating firewall rules to block offending IPs
  4. Unbanning IPs after a specified time period

Key Components

  • Filters: Regular expressions that identify malicious patterns in logs
  • Jails: Service-specific configurations that apply filters to log files
  • Actions: Commands executed when a filter matches (typically firewall bans)
  • Backend: The log monitoring system (polling, gamin, systemd)

How Fail2ban Works

Log File → Filter (Regex) → Detection → Action (Ban) → Unban (After Time)
     ↑                                                           ↓
     └───────────────────── Continuous Monitoring ←─────────────┘

Installing Fail2ban on AlmaLinux

Prerequisites

# Update system
sudo dnf update -y

# Install EPEL repository
sudo dnf install -y epel-release

# Ensure firewalld is installed and running
sudo dnf install -y firewalld
sudo systemctl enable --now firewalld

Installation

# Install fail2ban and dependencies
sudo dnf install -y fail2ban fail2ban-systemd

# Additional tools
sudo dnf install -y fail2ban-firewalld fail2ban-sendmail

# Verify installation
fail2ban-client --version

# Check package contents
rpm -ql fail2ban | head -20

Initial Setup

# Enable and start fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

# Check status
sudo systemctl status fail2ban

# View fail2ban process
ps aux | grep fail2ban

Directory Structure

/etc/fail2ban/
├── action.d/          # Action configurations
├── fail2ban.conf      # Main configuration
├── filter.d/          # Filter definitions
├── jail.conf          # Default jail configurations
├── jail.d/            # Custom jail configurations
├── jail.local         # Local jail overrides
└── paths-*.conf       # Log file path definitions

/var/log/fail2ban.log  # Fail2ban log file
/var/lib/fail2ban/     # Fail2ban database

Basic Configuration

Main Configuration File

# Create local configuration (never edit .conf files directly)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# Edit local configuration
sudo nano /etc/fail2ban/jail.local

Essential Global Settings

[DEFAULT]
# Ban duration (seconds)
bantime = 3600

# Time window for maxretry
findtime = 600

# Number of failures before ban
maxretry = 5

# Ignore IP addresses
ignoreip = 127.0.0.1/8 ::1 192.168.1.0/24

# Email notifications
destemail = [email protected]
sender = [email protected]
mta = sendmail

# Action to take
action = %(action_mwl)s

# Backend for log monitoring
backend = systemd

Understanding Configuration Options

# Ban time options
bantime = 1h       # 1 hour
bantime = 1d       # 1 day
bantime = 1w       # 1 week
bantime = -1       # Permanent ban

# Find time options
findtime = 10m     # 10 minutes
findtime = 1h      # 1 hour
findtime = 1d      # 1 day

# Action options
action = %(action_)s              # Ban only
action = %(action_mw)s            # Ban and email
action = %(action_mwl)s           # Ban, email with logs
action = %(action_xarf)s          # Ban with X-ARF report

Backend Configuration

# Available backends
backend = auto     # Auto-detect
backend = systemd  # For systemd journal
backend = polling  # Traditional log files
backend = gamin    # File alteration monitor

# Backend-specific options
[DEFAULT]
# For systemd backend
backend = systemd
journalmatch = _SYSTEMD_UNIT=sshd.service

# For polling backend
backend = polling
logencoding = utf-8

Working with Jails

Understanding Jails

Jails are the core concept in Fail2ban. Each jail:

  • Monitors specific log files
  • Uses specific filters
  • Takes specific actions
  • Has individual settings

Enabling Basic Jails

# SSH jail
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
bantime = 1h

# HTTP authentication
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = %(apache_error_log)s
maxretry = 5

# Nginx
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = %(nginx_error_log)s
maxretry = 3

Creating Custom Jails

Create /etc/fail2ban/jail.d/custom.local:

# Custom application jail
[myapp]
enabled = true
filter = myapp
port = 8080
logpath = /var/log/myapp/access.log
maxretry = 5
findtime = 10m
bantime = 30m
action = %(action_mwl)s

# Multiple ports
[multi-port-app]
enabled = true
filter = multi-port
port = 8080,8081,8082
logpath = /var/log/app/*.log
maxretry = 10

# Custom ban action
[aggressive-ssh]
enabled = true
filter = sshd
port = ssh
logpath = %(sshd_log)s
maxretry = 2
findtime = 1d
bantime = 1w

Jail Status Management

# View all jails
sudo fail2ban-client status

# View specific jail status
sudo fail2ban-client status sshd

# Start/stop jail
sudo fail2ban-client start sshd
sudo fail2ban-client stop sshd

# Reload jail
sudo fail2ban-client reload sshd

# Add IP to jail manually
sudo fail2ban-client set sshd banip 192.168.1.100

# Unban IP
sudo fail2ban-client set sshd unbanip 192.168.1.100

Creating Custom Filters

Filter Basics

Create /etc/fail2ban/filter.d/myapp.conf:

[Definition]
# Failure regex
failregex = ^<HOST> .* "POST /login HTTP/.*" 401
            ^Failed login from <HOST>
            ^Authentication failed for .* from <HOST>

# Ignore regex
ignoreregex = 

# Date pattern
datepattern = %%Y-%%m-%%d %%H:%%M:%%S

# Log prefix pattern
prefregex = ^\s*\[<F-TIMESTAMP>%%Y-%%m-%%d %%H:%%M:%%S</F-TIMESTAMP>\]

Advanced Filter Examples

WordPress Filter

# /etc/fail2ban/filter.d/wordpress.conf
[Definition]
failregex = ^<HOST> .* "POST .*wp-login\.php HTTP/.*" 200
            ^<HOST> .* "POST .*xmlrpc\.php HTTP/.*" 200
            Authentication failure for .* from <HOST>
            Blocked authentication attempt for .* from <HOST>
            Pingback error .* generated from <HOST>

ignoreregex = 

# Multiline support
maxlines = 2

API Rate Limiting Filter

# /etc/fail2ban/filter.d/api-rate-limit.conf
[Definition]
# Match API calls exceeding rate limits
failregex = ^<HOST> .* "(?:GET|POST) /api/.* HTTP/.*" 429
            ^Rate limit exceeded for <HOST>
            ^<HOST>.*Too many requests

ignoreregex = ^<HOST> .* "(?:GET|POST) /api/health.* HTTP/.*" 

datepattern = %%Y-%%m-%%d %%H:%%M:%%S

Custom Application Filter

# /etc/fail2ban/filter.d/custom-app.conf
[INCLUDES]
before = common.conf

[Definition]
_daemon = myapp

# Multiple failure patterns
failregex = ^%(__prefix_line)sAuthentication failed for .* from <HOST>$
            ^%(__prefix_line)sInvalid credentials from <HOST>$
            ^%(__prefix_line)sFailed (?:password|login) for .* from <HOST>$
            ^%(__prefix_line)sConnection refused from <HOST>$

# Date extraction
datepattern = ^[^\[]*\[({DATE})
              {^LN-BEG}

# Additional options
journalmatch = _SYSTEMD_UNIT=myapp.service

Testing Filters

# Test filter against log file
sudo fail2ban-regex /var/log/secure sshd

# Test custom filter
sudo fail2ban-regex /var/log/myapp/access.log /etc/fail2ban/filter.d/myapp.conf

# Verbose testing
sudo fail2ban-regex -v /var/log/nginx/access.log nginx-noscript

# Test with specific date pattern
sudo fail2ban-regex --datepattern "%%Y-%%m-%%d %%H:%%M:%%S" /var/log/custom.log myfilter

Configuring Actions

Understanding Actions

Actions define what happens when an IP is banned. Common actions include:

  • Firewall rules (iptables, firewalld, nftables)
  • Email notifications
  • Custom scripts
  • External service integration

Basic Action Configuration

# /etc/fail2ban/action.d/custom-firewall.conf
[Definition]
actionstart = echo "Starting fail2ban jail %(name)s" >> /var/log/fail2ban-actions.log

actionstop = echo "Stopping fail2ban jail %(name)s" >> /var/log/fail2ban-actions.log

actioncheck = firewall-cmd --state

actionban = firewall-cmd --add-rich-rule='rule family="ipv4" source address="<ip>" drop'
            echo "Banned <ip> in jail %(name)s" >> /var/log/fail2ban-actions.log

actionunban = firewall-cmd --remove-rich-rule='rule family="ipv4" source address="<ip>" drop'
              echo "Unbanned <ip> from jail %(name)s" >> /var/log/fail2ban-actions.log

Email Notification Actions

# Enhanced email notifications
[Definition]
actionstart = 
actionstop = 
actioncheck = 

actionban = printf %%b "Subject: [Fail2ban] %(name)s: banned <ip>
            From: %(sender)s
            To: %(destemail)s
            
            Hi,
            
            The IP <ip> has just been banned by Fail2ban after
            <failures> attempts against %(name)s.
            
            Additional Info:
            Jail: %(name)s
            Failures: <failures>
            Log Messages:
            <matches>
            
            Regards,
            Fail2ban" | /usr/sbin/sendmail -f %(sender)s %(destemail)s

actionunban = 

Custom Script Actions

# /etc/fail2ban/action.d/custom-script.conf
[Definition]
actionban = /usr/local/bin/ban-notify.sh <ip> <name> <failures>
actionunban = /usr/local/bin/unban-notify.sh <ip> <name>

# Create the script
sudo nano /usr/local/bin/ban-notify.sh
#!/bin/bash
# /usr/local/bin/ban-notify.sh

IP=$1
JAIL=$2
FAILURES=$3
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

# Log to database
mysql -u fail2ban -p'password' fail2ban_db << EOF
INSERT INTO bans (ip, jail, failures, timestamp) 
VALUES ('$IP', '$JAIL', $FAILURES, '$TIMESTAMP');
EOF

# Send to monitoring system
curl -X POST https://monitoring.example.com/api/ban \
     -H "Content-Type: application/json" \
     -d "{\"ip\":\"$IP\",\"jail\":\"$JAIL\",\"failures\":$FAILURES}"

# Update GeoIP database
geoiplookup $IP >> /var/log/fail2ban-geoip.log

Cloudflare Integration Action

# /etc/fail2ban/action.d/cloudflare.conf
[Definition]
actionstart = 
actionstop = 
actioncheck = 

actionban = curl -X POST "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules" \
                 -H "X-Auth-Email: <cfuser>" \
                 -H "X-Auth-Key: <cftoken>" \
                 -H "Content-Type: application/json" \
                 --data '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Fail2ban %(name)s"}'

actionunban = ID=$(curl -X GET "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules?mode=block&configuration.target=ip&configuration.value=<ip>" \
                        -H "X-Auth-Email: <cfuser>" \
                        -H "X-Auth-Key: <cftoken>" \
                        -H "Content-Type: application/json" | jq -r '.result[0].id')
              curl -X DELETE "https://api.cloudflare.com/client/v4/user/firewall/access_rules/rules/$ID" \
                   -H "X-Auth-Email: <cfuser>" \
                   -H "X-Auth-Key: <cftoken>"

[Init]
cfuser = [email protected]
cftoken = your-api-token

Service-Specific Configurations

SSH Protection

# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
findtime = 10m
bantime = 1h

# Aggressive SSH protection
[sshd-aggressive]
enabled = true
port = ssh
filter = sshd[mode=aggressive]
logpath = %(sshd_log)s
maxretry = 2
findtime = 1d
bantime = 1w

# SSH DDoS protection
[sshd-ddos]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s
maxretry = 10
findtime = 60
bantime = 1h

Web Server Protection

# Apache protection
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = %(apache_error_log)s

[apache-badbots]
enabled = true
port = http,https
filter = apache-badbots
logpath = %(apache_access_log)s
maxretry = 1

[apache-noscript]
enabled = true
port = http,https
filter = apache-noscript
logpath = %(apache_error_log)s

[apache-overflows]
enabled = true
port = http,https
filter = apache-overflows
logpath = %(apache_error_log)s
maxretry = 2

# Nginx protection
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = %(nginx_error_log)s

[nginx-noscript]
enabled = true
port = http,https
filter = nginx-noscript
logpath = %(nginx_access_log)s

[nginx-badbots]
enabled = true
port = http,https
filter = nginx-badbots
logpath = %(nginx_access_log)s
maxretry = 2

[nginx-noproxy]
enabled = true
port = http,https
filter = nginx-noproxy
logpath = %(nginx_access_log)s
maxretry = 2

Mail Server Protection

# Postfix
[postfix]
enabled = true
port = smtp,submission
filter = postfix
logpath = %(postfix_log)s

[postfix-sasl]
enabled = true
port = smtp,submission,imap,imaps,pop3,pop3s
filter = postfix[mode=auth]
logpath = %(postfix_log)s

# Dovecot
[dovecot]
enabled = true
port = pop3,pop3s,imap,imaps,submission,sieve
filter = dovecot
logpath = %(dovecot_log)s

Database Protection

# MySQL/MariaDB
[mysqld-auth]
enabled = true
port = 3306
filter = mysqld-auth
logpath = /var/log/mariadb/mariadb.log
maxretry = 5

# PostgreSQL
[postgresql]
enabled = true
port = 5432
filter = postgresql
logpath = /var/log/postgresql/*.log
maxretry = 5

# MongoDB
[mongodb-auth]
enabled = true
port = 27017
filter = mongodb-auth
logpath = /var/log/mongodb/mongod.log
maxretry = 3

Custom Application Protection

# Node.js Application
[nodejs-app]
enabled = true
port = 3000
filter = nodejs-app
logpath = /var/log/nodejs/app.log
maxretry = 10
findtime = 5m
bantime = 30m

# Python Flask/Django
[python-app]
enabled = true
port = 5000,8000
filter = python-app
logpath = /var/log/python-app/*.log
maxretry = 5

# Docker containers
[docker-app]
enabled = true
filter = docker-app
logpath = /var/lib/docker/containers/*/*-json.log
maxretry = 5
action = docker-firewall

Advanced Fail2ban Features

Persistent Banning

# Install sqlite support
sudo dnf install -y python3-sqlite

# Configure database
sudo nano /etc/fail2ban/fail2ban.local
[Definition]
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 1d

Recidive Jail (Repeat Offenders)

# /etc/fail2ban/jail.d/recidive.local
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
action = %(action_mwl)s
           %(action_badips)s[category="recidive"]
bantime = 1w
findtime = 1d
maxretry = 3

# Super recidive for persistent attackers
[recidive-superban]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
action = iptables-allports[blocktype=DROP]
bantime = -1  # Permanent ban
findtime = 1w
maxretry = 5

GeoIP Integration

# Install GeoIP
sudo dnf install -y GeoIP GeoIP-data

# Create GeoIP filter
sudo nano /etc/fail2ban/filter.d/geoip-ban.conf
[Definition]
failregex = <HOST> .* Country: (?:CN|RU|KP)
ignoreregex = 

Rate Limiting

# HTTP rate limiting
[http-rate-limit]
enabled = true
filter = http-rate-limit
action = iptables-multiport[name=HttpRateLimit, port="80,443"]
logpath = /var/log/nginx/access.log
findtime = 60
maxretry = 100
bantime = 600

# API rate limiting
[api-rate-limit]
enabled = true
filter = api-rate
action = iptables-multiport[name=ApiLimit, port="8080"]
logpath = /var/log/api/access.log
findtime = 300
maxretry = 50
bantime = 3600

Whitelist Management

# Global whitelist
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1
           192.168.0.0/16
           10.0.0.0/8
           172.16.0.0/12
           
# Dynamic whitelist script
sudo nano /usr/local/bin/fail2ban-whitelist.sh
#!/bin/bash
# Update whitelist from external source
curl -s https://example.com/whitelist.txt > /tmp/whitelist.txt
fail2ban-client set sshd addignoreip $(cat /tmp/whitelist.txt | tr '\n' ' ')

Monitoring and Management

Log Analysis

# View fail2ban logs
sudo tail -f /var/log/fail2ban.log

# Search for specific IP
sudo grep "192.168.1.100" /var/log/fail2ban.log

# Count bans per jail
sudo grep "Ban" /var/log/fail2ban.log | awk '{print $7}' | sort | uniq -c

# Recent bans
sudo grep "Ban" /var/log/fail2ban.log | tail -20

Status Monitoring

# Overall status
sudo fail2ban-client status

# Detailed jail status
sudo fail2ban-client status sshd

# Get all banned IPs
for jail in $(sudo fail2ban-client status | grep "Jail list" | sed 's/.*://;s/,//g'); do
    echo "=== $jail ==="
    sudo fail2ban-client status $jail | grep -A 100 "Banned IP"
done

# Monitor in real-time
watch -n 1 'sudo fail2ban-client status | grep -A 3 "Jail list"'

Creating Monitoring Scripts

#!/bin/bash
# /usr/local/bin/fail2ban-monitor.sh

# Generate HTML report
cat > /var/www/html/fail2ban-status.html << EOF
<!DOCTYPE html>
<html>
<head>
    <title>Fail2ban Status</title>
    <meta http-equiv="refresh" content="60">
</head>
<body>
    <h1>Fail2ban Status Report</h1>
    <p>Generated: $(date)</p>
    
    <h2>Active Jails</h2>
    <pre>$(sudo fail2ban-client status)</pre>
    
    <h2>Recent Bans</h2>
    <pre>$(sudo grep "Ban" /var/log/fail2ban.log | tail -50)</pre>
    
    <h2>Statistics</h2>
    <pre>
    Total Bans Today: $(sudo grep "Ban" /var/log/fail2ban.log | grep "$(date +%Y-%m-%d)" | wc -l)
    Total Unbans Today: $(sudo grep "Unban" /var/log/fail2ban.log | grep "$(date +%Y-%m-%d)" | wc -l)
    </pre>
    
    <h2>Top Banned IPs</h2>
    <pre>$(sudo grep "Ban" /var/log/fail2ban.log | awk '{print $8}' | sort | uniq -c | sort -rn | head -20)</pre>
</body>
</html>
EOF

Database Queries

# Query SQLite database
sudo sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "SELECT * FROM bans ORDER BY timeofban DESC LIMIT 10;"

# Count bans per jail
sudo sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "SELECT jail, COUNT(*) as count FROM bans GROUP BY jail ORDER BY count DESC;"

# Find repeat offenders
sudo sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "SELECT ip, COUNT(*) as count FROM bans GROUP BY ip HAVING count > 5 ORDER BY count DESC;"

Integration with Firewall

FirewallD Integration

# /etc/fail2ban/jail.local
[DEFAULT]
banaction = firewallcmd-ipset
banaction_allports = firewallcmd-allports

# Rich rules action
[sshd]
action = %(action_)s
         firewallcmd-rich-rules[name=SSH]

Custom FirewallD Actions

# /etc/fail2ban/action.d/firewallcmd-custom.conf
[Definition]
actionstart = firewall-cmd --permanent --new-ipset=<name> --type=hash:ip
              firewall-cmd --permanent --zone=drop --add-source=ipset:<name>
              firewall-cmd --reload

actionstop = firewall-cmd --permanent --zone=drop --remove-source=ipset:<name>
             firewall-cmd --permanent --delete-ipset=<name>
             firewall-cmd --reload

actionban = firewall-cmd --ipset=<name> --add-entry=<ip>

actionunban = firewall-cmd --ipset=<name> --remove-entry=<ip>

IPTables Integration

# Direct iptables action
[sshd-iptables]
enabled = true
filter = sshd
action = iptables[name=SSH, port=ssh, protocol=tcp]
logpath = %(sshd_log)s
maxretry = 3

NFTables Integration

# /etc/fail2ban/action.d/nftables-multiport.conf
[Definition]
actionstart = nft add table inet fail2ban
              nft add chain inet fail2ban input { type filter hook input priority 100 \; }
              nft add set inet fail2ban <name> { type ipv4_addr \; }
              nft add rule inet fail2ban input ip saddr @<name> drop

actionstop = nft delete table inet fail2ban

actionban = nft add element inet fail2ban <name> { <ip> }

actionunban = nft delete element inet fail2ban <name> { <ip> }

Performance Optimization

Backend Optimization

# Use systemd for better performance
[DEFAULT]
backend = systemd

# Optimize polling backend
[DEFAULT]
backend = polling
polltimer = 10  # Check every 10 seconds

Memory Management

# Monitor memory usage
ps aux | grep fail2ban
pmap -x $(pgrep fail2ban-server)

# Optimize database
sudo sqlite3 /var/lib/fail2ban/fail2ban.sqlite3 "VACUUM;"

# Limit log file size
sudo nano /etc/logrotate.d/fail2ban
/var/log/fail2ban.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 644 root root
    postrotate
        /usr/bin/fail2ban-client flushlogs 1>/dev/null || true
    endscript
}

Filter Optimization

# Optimize regex patterns
# Bad: .* (greedy)
# Good: [^:]* (specific)

# Use anchors
failregex = ^<HOST> - - \[.*\] ".*" 401

# Limit maxlines for multiline
maxlines = 5

# Use specific date patterns
datepattern = %%Y-%%m-%%d %%H:%%M:%%S

Troubleshooting

Common Issues

Service Won’t Start

# Check configuration
sudo fail2ban-client -d

# Test configuration
sudo fail2ban-client -t

# Check for syntax errors
sudo fail2ban-server -f

# View detailed errors
sudo journalctl -u fail2ban -n 50

Filter Not Matching

# Test filter manually
sudo fail2ban-regex /var/log/secure "Failed password for .* from <HOST>"

# Debug filter
sudo fail2ban-regex -D /var/log/secure sshd

# Check date format
sudo fail2ban-regex --print-date-format /var/log/secure

Bans Not Working

# Check if firewall is running
sudo firewall-cmd --state

# Verify action is correct
sudo fail2ban-client get sshd actionban

# Test action manually
sudo fail2ban-client set sshd banip 192.168.1.100

# Check firewall rules
sudo firewall-cmd --list-all
sudo iptables -L -n

Debug Mode

# Run in foreground with debug
sudo fail2ban-server -f -vvv

# Enable debug logging
sudo nano /etc/fail2ban/fail2ban.local
[Definition]
loglevel = DEBUG
logtarget = /var/log/fail2ban.log

Log Analysis for Troubleshooting

# Check for errors
sudo grep ERROR /var/log/fail2ban.log

# Find configuration issues
sudo grep "Unable to" /var/log/fail2ban.log

# Monitor specific jail
sudo tail -f /var/log/fail2ban.log | grep sshd

Best Practices

Security Best Practices

  1. Never disable fail2ban on production servers
  2. Keep ban times reasonable - balance security with usability
  3. Monitor logs regularly for false positives
  4. Maintain whitelist of trusted IPs
  5. Use recidive jail for repeat offenders
  6. Regular updates of fail2ban and filters

Configuration Best Practices

# Production-ready configuration
[DEFAULT]
# Whitelisting
ignoreip = 127.0.0.1/8 ::1
           # Add your management IPs

# Reasonable defaults
bantime = 1h
findtime = 10m
maxretry = 5

# Enable important jails
[sshd]
enabled = true
maxretry = 3

[recidive]
enabled = true
bantime = 1w

# Use systemd backend
backend = systemd

Maintenance Schedule

# Weekly tasks
# - Review banned IPs
# - Check for false positives
# - Update whitelists

# Monthly tasks
# - Analyze attack patterns
# - Update filters
# - Review jail effectiveness

# Quarterly tasks
# - Update fail2ban
# - Review and optimize rules
# - Performance assessment

Real-World Scenarios

Scenario 1: Protecting WordPress

# /etc/fail2ban/filter.d/wordpress-hard.conf
[Definition]
failregex = ^<HOST> .* "POST .*wp-login\.php
            ^<HOST> .* "POST .*xmlrpc\.php
            ^<HOST> .* "GET .*wp-admin.* 403
            ^<HOST> .* "GET .*\.php HTTP.* 404

# /etc/fail2ban/jail.d/wordpress.local
[wordpress-hard]
enabled = true
port = http,https
filter = wordpress-hard
logpath = /var/log/nginx/*access.log
maxretry = 3
findtime = 5m
bantime = 1d

Scenario 2: API Rate Limiting

# /etc/fail2ban/filter.d/api-bruteforce.conf
[Definition]
failregex = ^<HOST> .* "(?:GET|POST) /api/v1/auth.* 401
            ^<HOST> .* "(?:GET|POST) /api/.* 429

# /etc/fail2ban/jail.d/api.local
[api-bruteforce]
enabled = true
port = 8080,8443
filter = api-bruteforce
logpath = /var/log/api/access.log
maxretry = 20
findtime = 1m
bantime = 1h

Scenario 3: Multi-Service Protection

#!/bin/bash
# Setup comprehensive protection

# Enable essential jails
jails="sshd apache-auth apache-badbots apache-noscript 
       nginx-http-auth nginx-badbots dovecot postfix-sasl"

for jail in $jails; do
    sudo fail2ban-client set $jail enabled true
done

# Configure aggressive protection for critical services
cat > /etc/fail2ban/jail.d/critical.local << EOF
[sshd]
maxretry = 2
bantime = 1d

[dovecot]
maxretry = 3
bantime = 1d

[postfix-sasl]
maxretry = 3
bantime = 1d
EOF

sudo fail2ban-client reload

Scenario 4: Geographic Restrictions

# Block specific countries
cat > /etc/fail2ban/filter.d/geoblock.conf << 'EOF'
[Definition]
failregex = <HOST> .* Country: (CN|RU|KP|IR)
ignoreregex = 
EOF

cat > /etc/fail2ban/jail.d/geoblock.local << 'EOF'
[geoblock]
enabled = true
filter = geoblock
action = iptables-allports[blocktype=DROP]
logpath = /var/log/nginx/access.log
maxretry = 1
findtime = 1m
bantime = 1w
EOF

Conclusion

Fail2ban is an essential security tool for protecting AlmaLinux servers from brute-force attacks and malicious activities. When properly configured, it provides automated, real-time protection against various attack vectors while maintaining system accessibility for legitimate users.

Key takeaways:

  • Start with basic configurations and expand based on needs
  • Monitor logs regularly to avoid false positives
  • Keep filters and rules updated
  • Use appropriate ban times and retry limits
  • Integrate with existing security infrastructure
  • Regular maintenance ensures optimal protection

Remember that Fail2ban is just one layer of defense. Combine it with other security measures like firewalls, SELinux, regular updates, and good security practices for comprehensive protection.