← Back to DevBytes

Fail2Ban: Setup, Configuration, and Best Practices

Understanding Fail2Ban

Fail2Ban is an intrusion prevention framework written in Python that monitors log files for suspicious activity and automatically blocks offending IP addresses by updating firewall rules. It acts as a reactive shield, scanning system and application logs for patterns that match predefined failure events—such as repeated failed login attempts—and then temporarily banning the source IP for a configurable duration.

For developers and system administrators, Fail2Ban is an essential tool in the security stack. It transforms passive logging into active defense, drastically reducing the risk of brute-force attacks against SSH, web applications, email servers, databases, and any service that writes authentication failures to disk. By automating the ban and unban process, it provides a low-maintenance layer of protection that complements other security measures like strong passwords, key-based authentication, and network firewalls.

Core Architecture and Components

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Fail2Ban operates through three main components working in concert:

When the Fail2Ban daemon starts, it reads the jail configurations, opens each specified log file, and continuously tails it. As new log lines arrive, each line is tested against the associated filter's regex patterns. If a line matches, the incident is recorded along with the IP address. Once the number of failures for a single IP exceeds the maxretry threshold within the findtime window, the action is triggered to ban that IP for bantime seconds. After the ban period expires, Fail2Ban automatically removes the block, ensuring that temporary or dynamic IP addresses do not become permanently locked out.

Installation

Fail2Ban is available in the default package repositories of most major Linux distributions. The installation steps vary slightly, but the core configuration is identical across platforms.

Debian / Ubuntu

sudo apt update
sudo apt install fail2ban -y

RHEL / CentOS / Fedora

# On RHEL 8+ / CentOS Stream / Fedora
sudo dnf install fail2ban -y

# On older CentOS 7 (EPEL repository required)
sudo yum install epel-release -y
sudo yum install fail2ban -y

Starting and Enabling the Service

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

Once installed, the service runs in the background. The default configuration protects SSH and a few other services with sensible defaults, but to truly harden a system you'll customize the jails and filters.

Configuration File Strategy

Fail2Ban uses a layered configuration approach. The master configuration file /etc/fail2ban/jail.conf ships with the package and should never be edited directly because it will be overwritten during upgrades. Instead, create a jail.local file that overrides or extends the defaults. Fail2Ban merges the two files, with .local values taking precedence.

# Copy the distributed jail.conf as a starting point
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

You can now edit /etc/fail2ban/jail.local freely. Alternatively, if you prefer a more modular approach, place individual jail override files in /etc/fail2ban/jail.d/ with the .local suffix. For example, /etc/fail2ban/jail.d/sshd.local will override only the [sshd] jail.

Setting Up Your First Custom Jails

Below we configure two essential jails: SSH (hardening the default) and a custom application jail for a Node.js web application that logs authentication failures.

Hardening SSH

The SSH jail is enabled by default, but you may want to adjust parameters for your environment.

[sshd]
enabled   = true
port      = ssh
filter    = sshd
logpath   = /var/log/auth.log
maxretry  = 3
findtime  = 600
bantime   = 3600
action    = %(action_)s

After editing the jail.local file, restart Fail2Ban:

sudo systemctl restart fail2ban

Custom Jail for a Node.js Application

Imagine a Node.js app that logs failed login attempts in JSON format to /var/log/myapp/auth.log. Each log line looks like:

{"timestamp":"2025-03-21T10:15:00Z","level":"error","message":"Authentication failed for user admin from IP 203.0.113.45"}

We need to create a custom filter that extracts the IP from this JSON string.

Step 1: Create the filter file

sudo nano /etc/fail2ban/filter.d/myapp-auth.conf

Insert the following regex definition:

[Definition]
failregex = ^\s*\{.*"message":"Authentication failed for user \S+ from IP ".*\}$
ignoreregex =

The failregex must match the log line and capture the IP address using the special placeholder <HOST>. Fail2Ban's regex engine recognizes <HOST> as an alias for a sophisticated pattern that matches IPv4, IPv6, and even domain names (when DNS resolution is enabled). The ignoreregex can be left empty or used to exclude certain patterns (like trusted IPs).

Step 2: Define the jail in jail.local

[myapp-auth]
enabled   = true
port      = 3000
filter    = myapp-auth
logpath   = /var/log/myapp/auth.log
maxretry  = 5
findtime  = 300
bantime   = 1800
action    = %(action_)s

Here port is set to 3000, which is the application's listening port. This ensures the firewall rule blocks access to that specific service rather than all ports. If you want to ban the IP globally, you can set port = 0 or use an action that bans across all ports.

Step 3: Test the filter

Before activating a new jail, always test the filter against your actual logs to avoid false positives or missing matches. Fail2Ban ships with the fail2ban-regex tool for this purpose.

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

The output shows matched lines, the extracted IPs, and detailed statistics. If the regex doesn't match, you'll see zero matches and can refine the filter accordingly.

Step 4: Restart and verify

sudo systemctl restart fail2ban
sudo fail2ban-client status myapp-auth

The status command returns the jail's current state, including the list of currently banned IPs.

Advanced Filter Techniques

Filters can include multiple failregex lines, optional pre-filter date patterns, and even leverage Python's re module flags. Here are a few practical scenarios.

Multiple Failregex Patterns

When a service logs failures in several different formats, list all patterns under failregex, one per line:

[Definition]
failregex = ^\s*Failed password for .* from  port \d+ \w+$
            ^\s*Connection closed by authenticating user .*  port \d+ \[preauth\]$
            ^\s*authentication failure; .* rhost=(?:$| )

Using Regex Flags

To make patterns case-insensitive, add the (?i) inline modifier or use the ignoreregex with flags:

[Definition]
failregex = (?i)^\s*error: authentication failed from $

Handling JSON Logs with Escaped Quotes

If your JSON logs contain escaped double quotes, the regex must account for them. For example:

[Definition]
failregex = ^\{.*"message":"Authentication failed for user \S+ from IP ".*\}$

Always test with fail2ban-regex using a sample log file that contains both successes and failures to ensure accuracy.

Actions and Ban Enforcement

Actions determine how an IP is banned. The default action %(action_)s usually references action_ which in turn expands to iptables-allports or iptables-multiport depending on the distribution. You can customize actions extensively.

Using Firewalld Instead of iptables

On systems using firewalld (RHEL, CentOS 8+, Fedora), change the default action:

action = %(action_firewalld)s

This creates a direct firewalld rule that blocks the offending IP, integrated with the firewalld zone management.

Custom Action with Email Alert

You can chain actions by defining a custom action that first bans via iptables and then sends an email to the administrator.

[Definition]
actionstart = iptables -N f2b-myapp
              iptables -A f2b-myapp -j RETURN
              iptables -I INPUT -p tcp --dport 3000 -j f2b-myapp
actionstop = iptables -D INPUT -p tcp --dport 3000 -j f2b-myapp
             iptables -F f2b-myapp
             iptables -X f2b-myapp
actioncheck = iptables -n -L INPUT | grep -q "f2b-myapp"
actionban = iptables -I f2b-myapp 1 -s  -j DROP
            echo "Banned  for myapp" | mail -s "Fail2Ban Alert" admin@example.com
actionunban = iptables -D f2b-myapp -s  -j DROP
             echo "Unbanned  for myapp" | mail -s "Fail2Ban Unban" admin@example.com

Save this as /etc/fail2ban/action.d/myapp-email.conf and then reference it in the jail:

action = myapp-email

Managing Bans and Monitoring

Fail2Ban provides a command-line client, fail2ban-client, for real-time management and monitoring.

Checking Jail Status

sudo fail2ban-client status                # List all jails
sudo fail2ban-client status sshd           # Detailed status of the SSH jail

Manual Banning and Unbanning

sudo fail2ban-client set sshd banip 192.0.2.33   # Manually ban an IP
sudo fail2ban-client set sshd unbanip 192.0.2.33 # Remove a ban

Manual bans respect the jail's bantime and will expire automatically.

Viewing the Fail2Ban Log

Fail2Ban's own activity is logged to /var/log/fail2ban.log. Tail it to watch bans in real time:

sudo tail -f /var/log/fail2ban.log

You'll see entries like:

2025-03-21 12:00:00,123 fail2ban.actions        [12345]: NOTICE [sshd] Ban 203.0.113.45

Best Practices for Developers

Conclusion

Fail2Ban is a lightweight yet powerful tool that turns your application's logs into an automated defense system. By understanding its filter-action-jail architecture, you can extend protection beyond SSH to virtually any service that logs authentication failures—custom web apps, databases, FTP servers, and more. The key to success lies in crafting precise regular expressions, testing them thoroughly, and tuning parameters to balance security with usability. With the practices outlined here, you can deploy Fail2Ban with confidence, significantly reducing the attack surface of your systems while keeping maintenance overhead minimal.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles