Introduction

Fail2Ban is a server intrusion prevention framework that actively monitors your server's log files (like SSH, FTP, or web server logs) for suspicious activity. It looks for patterns, such as multiple failed login attempts or repeated 404 errors indicating a bot is scanning the site.Once a predefined threshold is met, it automatically updates the server's firewall (like iptables, nftables or UFW) to ban the malicious IP address for a specified amount of time. Fail2ban was originally designed to block a series of unsuccessful login attempts via SSH, but unless you absolutely need password authentication, it is far more secure to use public key authentication, disable root login and disable password authentication. The package includes a variety of filters you can choose from (in /etc/fail2ban/filter.d) as well as the ability to create your own. If you have ModSecurity installed, Fail2ban can be configured to monitor the ModSecurity audit log to ban attackers at the system level.

Prerequisites:

Ubuntu 26.04 installed on a VPS or Bare-Metal host
Nginx 1.14.0 or later
User with sudo privileges
Optional: ModSecurity strongly recommended

Start the Fail2ban set up

Install Fail2ban
sudo apt install fail2ban

Start the service and make it start on boot
sudo systemctl enable --now fail2ban.service

Verify that the service is running and status is OK
sudo systemctl status fail2ban.service

Output
● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/fail2ban.service.d └─override.conf Active: active (running) since Fri 2026-07-03 17:27:39 EDT; 1 day 19h ago Invocation: ae8e5e48a08c4b96b7a4d760bf739551 Docs: man:fail2ban(1) Main PID: 1677 (fail2ban-server) Tasks: 17 (limit: 15092) Memory: 28.4M (peak: 41.7M) CPU: 8min 54.616s CGroup: /system.slice/fail2ban.service └─1677 /usr/bin/python3 /usr/bin/fail2ban-server -xf start Jul 04 00:00:00 example.com systemd[1]: Started fail2ban.service - Fail2Ban Service. Jul 04 00:00:00 example.com fail2ban-server[28237]: Server ready

Configuration Files and loading order
/etc/fail2ban/

Loading Order:
fail2ban.conf:  The primary configuration file that controls the operational behavior and global settings of the Fail2ban daemon itself.

jail.conf:  Jails defining combinations of Filters with Actions.

jjail.d/*.conf:  (in alphabetical order)

jail.d/*.local:  (in alphabetical order)

See JAIL.CONF(5) manpages for further reference

By default, fail2ban ships with a jail.conf file. It is recommended not to modify this file, as it can be overwritten in updates. Best practice is to create a jail.local file.

Create the jail.local Configuration File

Go to the fail2ban directory and copy the jail.conf to jail.local

cd /etc/fail2ban
sudo cp jail.conf jail.local

Edit the jail.local file

sudo nano /etc/fail2ban/jail.local

We are going to edit the DEFAULTS to conform with your system. As you can see, there are many areas of this file that are not applicable for your system. You can leave them there for now.

As the focus of this post is for Ubuntu and Nginx, we will be making modifications respectively. To get to a specific area of the file, you can search in nano by pressing ctl-f or ctl-w.

One of the first items to look at is the list of clients that are not subject to the fail2ban policies. This is set by the ignoreip directive. Add your own IP address or network to the list of exceptions to avoid locking yourself out. Find the line that says #ignoreip = 127.0.0.1/8 ::1 and un-comment and add your IP address (separated by spaces).

[DEFAULT]
...
ignoreip = 127.0.0.1/8 ::1
           your_ip_address
           additional IP address (optional)

[DEFAULT]
...
bantime = 1h

The "bantime" is the amount of time that a host is banned. It can be set as an integer in seconds or time abbreviation format (m - minutes, h - hours, d - days, w - weeks, mo - months, y - years). Set the default bantime to 1h.

The next 2 items work together.

[DEFAULT]
...
findtime  = 10m
maxretry = 5

"maxretry" is the number of failures within the findtime before a host gets banned.


By default, Fail2Ban uses iptables to block malicious IP addresses on Ubuntu. However, because Ubuntu uses UFW (Uncomplicated Firewall) as its standard firewall manager, it is highly recommended and common practice to configure Fail2Ban to utilize UFW for applying block rules instead.

[DEFAULT]
...
# Remove or comment
banaction = iptables-multiport
banaction_allports = iptables-allports

# Add
# Ban hosts using UFW instead of standard iptables
banaction = ufw[blocktype=deny]
banaction_allports = ufw[blocktype=deny]

If you have a Postfix mail server installed, modify the following settings to conform with your system.

[DEFAULT]
...
mta = postfix
sendername = Server Fail2Ban
sender = fail2ban@yourdomain.com
# ban & send an e-mail with whois report to the destemail.
action = %(action_mw)s

Press ctl+o to save your changes. Next we will configure Fail2ban to block Nginx specific attackers.

Configure Fail2ban for NGINX

Move down to the JAILS section and find [nginx-http-auth]. Since all the jails are disabled by default, we will have to enable it and assign the filter. The filter is the name of the .conf file in the filter.d directory without the file extension(.conf). This file contains the expressions that evaluate the line in the log.

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

This jail is designed to block failed login attempts


Next find the jail [nginx-botsearch]. Enable it and assign the filter.

This jail is designed to block web requests for selected URLs that don't exist

All of the jails can be customized for your needs. You can specify different bantime, findtime, maxretry, banaction, logpath, etc.
[nginx-botsearch]
enabled   = true
port      = http,https
filter    = nginx-botsearch
banaction = ufw[blocktype=deny]
backend   = auto
logpath   = /var/log/nginx/error.log
            /var/log/your-website/error.log
maxretry  = 3
bantime   = 1d
journalmatch = _SYSTEMD_UNIT=nginx.service
Enable and assign the filter to [nginx-bad-request] and [nginx-forbidden].

After making your edits, save(ctl-o) and exit(ctl-x) nano. Always restart the service after making edits.

sudo systemctl restart fail2ban

Test and Verify the Fail2ban Configuration

Check the status of the service. It will show error if there are any typos from your edits.

sudo systemctl status fail2ban
Output
● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/fail2ban.service.d └─override.conf Active: active (running) since Wed 2026-07-08 14:00:25 EDT; 5h 36min ago Invocation: b10c0c51b4074f9fbddc579a3616c623 Docs: man:fail2ban(1) Main PID: 6588 (fail2ban-server) Tasks: 25 (limit: 15092) Memory: 20.7M (peak: 32.5M) CPU: 1min 47.540s CGroup: /system.slice/fail2ban.service └─6588 /usr/bin/python3 /usr/bin/fail2ban-server -xf start Jul 08 14:00:25 example.com systemd[1]: Started fail2ban.service - Fail2Ban Service. Jul 08 14:00:25 example.com fail2ban-server[6588]: Server ready

Verify that your jails are enabled.

sudo fail2ban-client status
Output
Status |- Number of jail: 5 `- Jail list: nginx-bad-request, nginx-botsearch, nginx-forbidden, nginx-http-auth sshd

To check the status of a specific jail:

sudo fail2ban-client status nginx-botsearch
Output
Status for the jail: nginx-botsearch |- Filter | |- Currently failed: 0 | |- Total failed: 0 | `- File list: /var/log/nginx/error.log `- Actions |- Currently banned: 1 |- Total banned: 1 `- Banned IP list: 123.456.789.100

Note: The fail2ban log file /var/log/fail2ban.log will show all matches and bans. If your jail is active but failing to ban offending actors, look through your server logs specified in the logpath = in your jails to locate where your system's errors are printing. You can verify your regex patterns directly against that specific log with the fail2ban-regex command.
Syntax:  sudo fail2ban-regex [full-path-to-log-file] [full path to filter file]

The following command reads the Nginx error log for patterns matching the nginx-botsearch [definition]


sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-botsearch.conf

 Note: The default Fail2ban nginx-http-auth jail will not work for WordPress wp-admin because WordPress handles its own login authentication at the application level and natively returns an HTTP 200 status code even for failed login attempts.
The nginx-http-auth filter only scans Nginx error logs for 401 Unauthorized errors generated by Nginx's built-in Basic HTTP Authentication (auth_basic). Because WordPress processes logins via wp-login.php without notifying Nginx of a failure, those failures are logged in the Nginx access log as HTTP 200 OK.

If you are hosting a WordPress website and you want to track native WordPress login screen attempts you must instruct Fail2ban to read the website access.log for POST requests targeting the login scripts.

Managing Fail2ban via the /etc/fail2ban/jail.d/ directory is the recommended approach for organizing custom jail configurations. It prevents package updates from overwriting your settings, keeps jails modular, and if it has a .local extension it is parsed in alphabetical order directly after the main /etc/fail2ban/jail.local so your custom jail will override the defaults in jail.local

Step 1: Build a custom WordPress login filter

Create a configuration file at /etc/fail2ban/filter.d/wordpress-auth.conf


Step 2: Create a new jail in your /etc/fail2ban/jail.d directory using your website's access.log as the logpath

sudo nano /etc/fail2ban/jail.d/wordpress-auth.local
[wordpress-auth]
enabled = true
port = http,https
filter = wordpress-auth
logpath = /var/log/your-website/access.log
maxretry = 5
findtime = 10m
bantime = 1h

Save and Restart Fail2ban

sudo systemctl restart fail2ban

Now verify your regex patterns directly against that specific log


sudo fail2ban-regex /var/log/your-website/access.log /etc/fail2ban/filter.d/wordpress-auth.conf

If your filter is working, you will see a positive number of matched. 0 matched means the filter is not working and needs modification(s). If you have matches, but the IP is not banned, adjust the maxretry and findtime.

If you are getting 0 matched on any of your filters, there are several steps you can take.

Check your log formats.
If no format is specified, Nginx uses the predefined combined format (with the exception of error logs):
log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

The following variables are most commonly used in log format definitions:


Predefined Error Log Format
Because the error log format is hardcoded into the Nginx source code, every error log entry consistently follows this structure:
Breakdown of Fields
Example of website error log

2026/07/09 02:03:46 [error] 21047#21047: *424 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 10.0.0.50, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "10.1.1.1"
Compare a log entry that should have matched with the failregex = in the filter(.conf)
1. Copy the log entry from logpath = from your jail into a text editor.
2. Copy the output of:
sudo cat /etc/fail2ban/filter.d/nginx-botsearch.conf
into the text editor below the log entry. Use the following document as a reference to translate the failregex = statement:


Fail2ban and ModSecurity

The combination of Fail2ban and ModSecurity provides a strong, layered defense. In this section, we will configure Fail2ban to monitor the ModSecurity audit log and block attacks before they hit the website. Next we will configure Fail2ban to monitor the Nginx error log specifically for ModSecurity: Access denied error

Build a custom Fail2ban ModSecurity filter
Create a configuration file /etc/fail2ban/filter.d/modsec.conf


Create a new jail in your /etc/fail2ban/jail.d directory using the ModSecurity audit log in the logpath

sudo nano /etc/fail2ban/jail.d/modsec.local
[modsec]
enabled = true
filter = modsec
port = http,https
banaction = ufw[blocktype=deny]
backend  = auto
journalmatch = _SYSTEMD_UNIT=nginx.service
logpath = /var/log/modsec_audit.log
logencoding = utf-8
maxretry = 3
findtime = 10m
bantime = 1d

Save and Restart Fail2ban

sudo systemctl restart fail2ban

Test your new jail


sudo fail2ban-regex /var/log/modsec_audit.log /etc/fail2ban/filter.d/modsec.conf

Build a custom Nginx ModSecurity filter
Create a configuration file at /etc/fail2ban/filter.d/nginx-modsec.conf


Create a new jail in your /etc/fail2ban/jail.d directory using the Nginx error log in the logpath

sudo nano /etc/fail2ban/jail.d/nginx-modsec.local
[nginx-modsec]
enabled  = true
port     = http,https
filter   = nginx-modsec
logpath  = /var/log/nginx/error.log
maxretry = 2
findtime = 10m
bantime  = 1d

Save(ctl-o ctl-x) and Restart Fail2ban

sudo systemctl restart fail2ban

Test your new jail


sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-modsec.conf

Conclusion

Hopefully, you have installed and configured Fail2ban and have a good understanding of how it works and how to customize fail2ban for your specific needs. The content in this post has been tested extensively in our test system. Each system is different, so you may need modifications to comply with yours. Feel free to leave a comment if you have any questions.

Introduction

Fail2Ban is a server intrusion prevention framework that actively monitors your server's log files (like SSH, FTP, or web server logs) for suspicious activity. It looks for patterns, such as multiple failed login attempts or repeated 404 errors indicating a bot is scanning the site.Once a predefined threshold is met, it automatically updates the server's firewall (like iptables, nftables or UFW) to ban the malicious IP address for a specified amount of time. Fail2ban was originally designed to block a series of unsuccessful login attempts via SSH, but unless you absolutely need password authentication, it is far more secure to use public key authentication, disable root login and disable password authentication. The package includes a variety of filters you can choose from (in /etc/fail2ban/filter.d) as well as the ability to create your own. If you have ModSecurity installed, Fail2ban can be configured to monitor the ModSecurity audit log to ban attackers at the system level.

Prerequisites:

Ubuntu 26.04 installed on a VPS or Bare-Metal host
Nginx 1.14.0 or later
User with sudo privileges
Optional: ModSecurity strongly recommended

Start the Fail2ban set up

Install Fail2ban
sudo apt install fail2ban

Start the service and make it start on boot
sudo systemctl enable --now fail2ban.service

Verify that the service is running and status is OK
sudo systemctl status fail2ban.service

Output
● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/fail2ban.service.d └─override.conf Active: active (running) since Fri 2026-07-03 17:27:39 EDT; 1 day 19h ago Invocation: ae8e5e48a08c4b96b7a4d760bf739551 Docs: man:fail2ban(1) Main PID: 1677 (fail2ban-server) Tasks: 17 (limit: 15092) Memory: 28.4M (peak: 41.7M) CPU: 8min 54.616s CGroup: /system.slice/fail2ban.service └─1677 /usr/bin/python3 /usr/bin/fail2ban-server -xf start Jul 04 00:00:00 example.com systemd[1]: Started fail2ban.service - Fail2Ban Service. Jul 04 00:00:00 example.com fail2ban-server[28237]: Server ready

Configuration Files and loading order
/etc/fail2ban/

Loading Order:
fail2ban.conf:  The primary configuration file that controls the operational behavior and global settings of the Fail2ban daemon itself.

jail.conf:  Jails defining combinations of Filters with Actions.

jjail.d/*.conf:  (in alphabetical order)

jail.d/*.local:  (in alphabetical order)

See JAIL.CONF(5) manpages for further reference

By default, fail2ban ships with a jail.conf file. It is recommended not to modify this file, as it can be overwritten in updates. Best practice is to create a jail.local file.

Create the jail.local Configuration File

Go to the fail2ban directory and copy the jail.conf to jail.local

cd /etc/fail2ban
sudo cp jail.conf jail.local

Edit the jail.local file

sudo nano /etc/fail2ban/jail.local

We are going to edit the DEFAULTS to conform with your system. As you can see, there are many areas of this file that are not applicable for your system. You can leave them there for now.

As the focus of this post is for Ubuntu and Nginx, we will be making modifications respectively. To get to a specific area of the file, you can search in nano by pressing ctl-f or ctl-w.

One of the first items to look at is the list of clients that are not subject to the fail2ban policies. This is set by the ignoreip directive. Add your own IP address or network to the list of exceptions to avoid locking yourself out. Find the line that says #ignoreip = 127.0.0.1/8 ::1 and un-comment and add your IP address (separated by spaces).

[DEFAULT]
...
ignoreip = 127.0.0.1/8 ::1
           your_ip_address
           additional IP address (optional)

[DEFAULT]
...
bantime = 1h

The "bantime" is the amount of time that a host is banned. It can be set as an integer in seconds or time abbreviation format (m - minutes, h - hours, d - days, w - weeks, mo - months, y - years). Set the default bantime to 1h.

The next 2 items work together.

[DEFAULT]
...
findtime  = 10m
maxretry = 5

"maxretry" is the number of failures within the findtime before a host gets banned.


By default, Fail2Ban uses iptables to block malicious IP addresses on Ubuntu. However, because Ubuntu uses UFW (Uncomplicated Firewall) as its standard firewall manager, it is highly recommended and common practice to configure Fail2Ban to utilize UFW for applying block rules instead.

[DEFAULT]
...
# Remove or comment
banaction = iptables-multiport
banaction_allports = iptables-allports

# Add
# Ban hosts using UFW instead of standard iptables
banaction = ufw[blocktype=deny]
banaction_allports = ufw[blocktype=deny]

If you have a Postfix mail server installed, modify the following settings to conform with your system.

[DEFAULT]
...
mta = postfix
sendername = Server Fail2Ban
sender = fail2ban@yourdomain.com
# ban & send an e-mail with whois report to the destemail.
action = %(action_mw)s

Press ctl+o to save your changes. Next we will configure Fail2ban to block Nginx specific attackers.

Configure Fail2ban for NGINX

Move down to the JAILS section and find [nginx-http-auth]. Since all the jails are disabled by default, we will have to enable it and assign the filter. The filter is the name of the .conf file in the filter.d directory without the file extension(.conf). This file contains the expressions that evaluate the line in the log.

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

This jail is designed to block failed login attempts


Next find the jail [nginx-botsearch]. Enable it and assign the filter.

This jail is designed to block web requests for selected URLs that don't exist

All of the jails can be customized for your needs. You can specify different bantime, findtime, maxretry, banaction, logpath, etc.
[nginx-botsearch]
enabled   = true
port      = http,https
filter    = nginx-botsearch
banaction = ufw[blocktype=deny]
backend   = auto
logpath   = /var/log/nginx/error.log
            /var/log/your-website/error.log
maxretry  = 3
bantime   = 1d
journalmatch = _SYSTEMD_UNIT=nginx.service
Enable and assign the filter to [nginx-bad-request] and [nginx-forbidden].

After making your edits, save(ctl-o) and exit(ctl-x) nano. Always restart the service after making edits.

sudo systemctl restart fail2ban

Test and Verify the Fail2ban Configuration

Check the status of the service. It will show error if there are any typos from your edits.

sudo systemctl status fail2ban
Output
● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/fail2ban.service.d └─override.conf Active: active (running) since Wed 2026-07-08 14:00:25 EDT; 5h 36min ago Invocation: b10c0c51b4074f9fbddc579a3616c623 Docs: man:fail2ban(1) Main PID: 6588 (fail2ban-server) Tasks: 25 (limit: 15092) Memory: 20.7M (peak: 32.5M) CPU: 1min 47.540s CGroup: /system.slice/fail2ban.service └─6588 /usr/bin/python3 /usr/bin/fail2ban-server -xf start Jul 08 14:00:25 example.com systemd[1]: Started fail2ban.service - Fail2Ban Service. Jul 08 14:00:25 example.com fail2ban-server[6588]: Server ready

Verify that your jails are enabled.

sudo fail2ban-client status
Output
Status |- Number of jail: 5 `- Jail list: nginx-bad-request, nginx-botsearch, nginx-forbidden, nginx-http-auth sshd

To check the status of a specific jail:

sudo fail2ban-client status nginx-botsearch
Output
Status for the jail: nginx-botsearch |- Filter | |- Currently failed: 0 | |- Total failed: 0 | `- File list: /var/log/nginx/error.log `- Actions |- Currently banned: 1 |- Total banned: 1 `- Banned IP list: 123.456.789.100

Note: The fail2ban log file /var/log/fail2ban.log will show all matches and bans. If your jail is active but failing to ban offending actors, look through your server logs specified in the logpath = in your jails to locate where your system's errors are printing. You can verify your regex patterns directly against that specific log with the fail2ban-regex command.
Syntax:  sudo fail2ban-regex [full-path-to-log-file] [full path to filter file]

The following command reads the Nginx error log for patterns matching the nginx-botsearch [definition]


sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-botsearch.conf

 Note: The default Fail2ban nginx-http-auth jail will not work for WordPress wp-admin because WordPress handles its own login authentication at the application level and natively returns an HTTP 200 status code even for failed login attempts.
The nginx-http-auth filter only scans Nginx error logs for 401 Unauthorized errors generated by Nginx's built-in Basic HTTP Authentication (auth_basic). Because WordPress processes logins via wp-login.php without notifying Nginx of a failure, those failures are logged in the Nginx access log as HTTP 200 OK.

If you are hosting a WordPress website and you want to track native WordPress login screen attempts you must instruct Fail2ban to read the website access.log for POST requests targeting the login scripts.

Managing Fail2ban via the /etc/fail2ban/jail.d/ directory is the recommended approach for organizing custom jail configurations. It prevents package updates from overwriting your settings, keeps jails modular, and if it has a .local extension it is parsed in alphabetical order directly after the main /etc/fail2ban/jail.local so your custom jail will override the defaults in jail.local

Step 1: Build a custom WordPress login filter

Create a configuration file at /etc/fail2ban/filter.d/wordpress-auth.conf


Step 2: Create a new jail in your /etc/fail2ban/jail.d directory using your website's access.log as the logpath

sudo nano /etc/fail2ban/jail.d/wordpress-auth.local
[wordpress-auth]
enabled = true
port = http,https
filter = wordpress-auth
logpath = /var/log/your-website/access.log
maxretry = 5
findtime = 10m
bantime = 1h

Save and Restart Fail2ban

sudo systemctl restart fail2ban

Now verify your regex patterns directly against that specific log


sudo fail2ban-regex /var/log/your-website/access.log /etc/fail2ban/filter.d/wordpress-auth.conf

If your filter is working, you will see a positive number of matched. 0 matched means the filter is not working and needs modification(s). If you have matches, but the IP is not banned, adjust the maxretry and findtime.

If you are getting 0 matched on any of your filters, there are several steps you can take.

Check your log formats.
If no format is specified, Nginx uses the predefined combined format (with the exception of error logs):
log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

The following variables are most commonly used in log format definitions:


Predefined Error Log Format
Because the error log format is hardcoded into the Nginx source code, every error log entry consistently follows this structure:
Breakdown of Fields
Example of website error log

2026/07/09 02:03:46 [error] 21047#21047: *424 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 10.0.0.50, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "10.1.1.1"
Compare a log entry that should have matched with the failregex = in the filter(.conf)
1. Copy the log entry from logpath = from your jail into a text editor.
2. Copy the output of:
sudo cat /etc/fail2ban/filter.d/nginx-botsearch.conf
into the text editor below the log entry. Use the following document as a reference to translate the failregex = statement:


Fail2ban and ModSecurity

The combination of Fail2ban and ModSecurity provides a strong, layered defense. In this section, we will configure Fail2ban to monitor the ModSecurity audit log and block attacks before they hit the website. Next we will configure Fail2ban to monitor the Nginx error log specifically for ModSecurity: Access denied error

Build a custom Fail2ban ModSecurity filter
Create a configuration file /etc/fail2ban/filter.d/modsec.conf


Create a new jail in your /etc/fail2ban/jail.d directory using the ModSecurity audit log in the logpath

sudo nano /etc/fail2ban/jail.d/modsec.local
[modsec]
enabled = true
filter = modsec
port = http,https
banaction = ufw[blocktype=deny]
backend  = auto
journalmatch = _SYSTEMD_UNIT=nginx.service
logpath = /var/log/modsec_audit.log
logencoding = utf-8
maxretry = 3
findtime = 10m
bantime = 1d

Save and Restart Fail2ban

sudo systemctl restart fail2ban

Test your new jail


sudo fail2ban-regex /var/log/modsec_audit.log /etc/fail2ban/filter.d/modsec.conf

Build a custom Nginx ModSecurity filter
Create a configuration file at /etc/fail2ban/filter.d/nginx-modsec.conf


Create a new jail in your /etc/fail2ban/jail.d directory using the Nginx error log in the logpath

sudo nano /etc/fail2ban/jail.d/nginx-modsec.local
[nginx-modsec]
enabled  = true
port     = http,https
filter   = nginx-modsec
logpath  = /var/log/nginx/error.log
maxretry = 2
findtime = 10m
bantime  = 1d

Save(ctl-o ctl-x) and Restart Fail2ban

sudo systemctl restart fail2ban

Test your new jail


sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-modsec.conf

Conclusion

Hopefully, you have installed and configured Fail2ban and have a good understanding of how it works and how to customize fail2ban for your specific needs. The content in this post has been tested extensively in our test system. Each system is different, so you may need modifications to comply with yours. Feel free to leave a comment if you have any questions.

Leave A Comment

Total Views: 206Daily Views: 15

Introduction

Fail2Ban is a server intrusion prevention framework that actively monitors your server's log files (like SSH, FTP, or web server logs) for suspicious activity. It looks for patterns, such as multiple failed login attempts or repeated 404 errors indicating a bot is scanning the site.Once a predefined threshold is met, it automatically updates the server's firewall (like iptables, nftables or UFW) to ban the malicious IP address for a specified amount of time. Fail2ban was originally designed to block a series of unsuccessful login attempts via SSH, but unless you absolutely need password authentication, it is far more secure to use public key authentication, disable root login and disable password authentication. The package includes a variety of filters you can choose from (in /etc/fail2ban/filter.d) as well as the ability to create your own. If you have ModSecurity installed, Fail2ban can be configured to monitor the ModSecurity audit log to ban attackers at the system level.

Prerequisites:

Ubuntu 26.04 installed on a VPS or Bare-Metal host
Nginx 1.14.0 or later
User with sudo privileges
Optional: ModSecurity strongly recommended

Start the Fail2ban set up

Install Fail2ban
sudo apt install fail2ban

Start the service and make it start on boot
sudo systemctl enable --now fail2ban.service

Verify that the service is running and status is OK
sudo systemctl status fail2ban.service

Output
● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/fail2ban.service.d └─override.conf Active: active (running) since Fri 2026-07-03 17:27:39 EDT; 1 day 19h ago Invocation: ae8e5e48a08c4b96b7a4d760bf739551 Docs: man:fail2ban(1) Main PID: 1677 (fail2ban-server) Tasks: 17 (limit: 15092) Memory: 28.4M (peak: 41.7M) CPU: 8min 54.616s CGroup: /system.slice/fail2ban.service └─1677 /usr/bin/python3 /usr/bin/fail2ban-server -xf start Jul 04 00:00:00 example.com systemd[1]: Started fail2ban.service - Fail2Ban Service. Jul 04 00:00:00 example.com fail2ban-server[28237]: Server ready

Configuration Files and loading order
/etc/fail2ban/

Loading Order:
fail2ban.conf:  The primary configuration file that controls the operational behavior and global settings of the Fail2ban daemon itself.

jail.conf:  Jails defining combinations of Filters with Actions.

jjail.d/*.conf:  (in alphabetical order)

jail.d/*.local:  (in alphabetical order)

See JAIL.CONF(5) manpages for further reference

By default, fail2ban ships with a jail.conf file. It is recommended not to modify this file, as it can be overwritten in updates. Best practice is to create a jail.local file.

Create the jail.local Configuration File

Go to the fail2ban directory and copy the jail.conf to jail.local

cd /etc/fail2ban
sudo cp jail.conf jail.local

Edit the jail.local file

sudo nano /etc/fail2ban/jail.local

We are going to edit the DEFAULTS to conform with your system. As you can see, there are many areas of this file that are not applicable for your system. You can leave them there for now.

As the focus of this post is for Ubuntu and Nginx, we will be making modifications respectively. To get to a specific area of the file, you can search in nano by pressing ctl-f or ctl-w.

One of the first items to look at is the list of clients that are not subject to the fail2ban policies. This is set by the ignoreip directive. Add your own IP address or network to the list of exceptions to avoid locking yourself out. Find the line that says #ignoreip = 127.0.0.1/8 ::1 and un-comment and add your IP address (separated by spaces).

[DEFAULT]
...
ignoreip = 127.0.0.1/8 ::1
           your_ip_address
           additional IP address (optional)

[DEFAULT]
...
bantime = 1h

The "bantime" is the amount of time that a host is banned. It can be set as an integer in seconds or time abbreviation format (m - minutes, h - hours, d - days, w - weeks, mo - months, y - years). Set the default bantime to 1h.

The next 2 items work together.

[DEFAULT]
...
findtime  = 10m
maxretry = 5

"maxretry" is the number of failures within the findtime before a host gets banned.


By default, Fail2Ban uses iptables to block malicious IP addresses on Ubuntu. However, because Ubuntu uses UFW (Uncomplicated Firewall) as its standard firewall manager, it is highly recommended and common practice to configure Fail2Ban to utilize UFW for applying block rules instead.

[DEFAULT]
...
# Remove or comment
banaction = iptables-multiport
banaction_allports = iptables-allports

# Add
# Ban hosts using UFW instead of standard iptables
banaction = ufw[blocktype=deny]
banaction_allports = ufw[blocktype=deny]

If you have a Postfix mail server installed, modify the following settings to conform with your system.

[DEFAULT]
...
mta = postfix
sendername = Server Fail2Ban
sender = fail2ban@yourdomain.com
# ban & send an e-mail with whois report to the destemail.
action = %(action_mw)s

Press ctl+o to save your changes. Next we will configure Fail2ban to block Nginx specific attackers.

Configure Fail2ban for NGINX

Move down to the JAILS section and find [nginx-http-auth]. Since all the jails are disabled by default, we will have to enable it and assign the filter. The filter is the name of the .conf file in the filter.d directory without the file extension(.conf). This file contains the expressions that evaluate the line in the log.

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

This jail is designed to block failed login attempts


Next find the jail [nginx-botsearch]. Enable it and assign the filter.

This jail is designed to block web requests for selected URLs that don't exist

All of the jails can be customized for your needs. You can specify different bantime, findtime, maxretry, banaction, logpath, etc.
[nginx-botsearch]
enabled   = true
port      = http,https
filter    = nginx-botsearch
banaction = ufw[blocktype=deny]
backend   = auto
logpath   = /var/log/nginx/error.log
            /var/log/your-website/error.log
maxretry  = 3
bantime   = 1d
journalmatch = _SYSTEMD_UNIT=nginx.service
Enable and assign the filter to [nginx-bad-request] and [nginx-forbidden].

After making your edits, save(ctl-o) and exit(ctl-x) nano. Always restart the service after making edits.

sudo systemctl restart fail2ban

Test and Verify the Fail2ban Configuration

Check the status of the service. It will show error if there are any typos from your edits.

sudo systemctl status fail2ban
Output
● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/fail2ban.service.d └─override.conf Active: active (running) since Wed 2026-07-08 14:00:25 EDT; 5h 36min ago Invocation: b10c0c51b4074f9fbddc579a3616c623 Docs: man:fail2ban(1) Main PID: 6588 (fail2ban-server) Tasks: 25 (limit: 15092) Memory: 20.7M (peak: 32.5M) CPU: 1min 47.540s CGroup: /system.slice/fail2ban.service └─6588 /usr/bin/python3 /usr/bin/fail2ban-server -xf start Jul 08 14:00:25 example.com systemd[1]: Started fail2ban.service - Fail2Ban Service. Jul 08 14:00:25 example.com fail2ban-server[6588]: Server ready

Verify that your jails are enabled.

sudo fail2ban-client status
Output
Status |- Number of jail: 5 `- Jail list: nginx-bad-request, nginx-botsearch, nginx-forbidden, nginx-http-auth sshd

To check the status of a specific jail:

sudo fail2ban-client status nginx-botsearch
Output
Status for the jail: nginx-botsearch |- Filter | |- Currently failed: 0 | |- Total failed: 0 | `- File list: /var/log/nginx/error.log `- Actions |- Currently banned: 1 |- Total banned: 1 `- Banned IP list: 123.456.789.100

Note: The fail2ban log file /var/log/fail2ban.log will show all matches and bans. If your jail is active but failing to ban offending actors, look through your server logs specified in the logpath = in your jails to locate where your system's errors are printing. You can verify your regex patterns directly against that specific log with the fail2ban-regex command.
Syntax:  sudo fail2ban-regex [full-path-to-log-file] [full path to filter file]

The following command reads the Nginx error log for patterns matching the nginx-botsearch [definition]


sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-botsearch.conf

 Note: The default Fail2ban nginx-http-auth jail will not work for WordPress wp-admin because WordPress handles its own login authentication at the application level and natively returns an HTTP 200 status code even for failed login attempts.
The nginx-http-auth filter only scans Nginx error logs for 401 Unauthorized errors generated by Nginx's built-in Basic HTTP Authentication (auth_basic). Because WordPress processes logins via wp-login.php without notifying Nginx of a failure, those failures are logged in the Nginx access log as HTTP 200 OK.

If you are hosting a WordPress website and you want to track native WordPress login screen attempts you must instruct Fail2ban to read the website access.log for POST requests targeting the login scripts.

Managing Fail2ban via the /etc/fail2ban/jail.d/ directory is the recommended approach for organizing custom jail configurations. It prevents package updates from overwriting your settings, keeps jails modular, and if it has a .local extension it is parsed in alphabetical order directly after the main /etc/fail2ban/jail.local so your custom jail will override the defaults in jail.local

Step 1: Build a custom WordPress login filter

Create a configuration file at /etc/fail2ban/filter.d/wordpress-auth.conf


Step 2: Create a new jail in your /etc/fail2ban/jail.d directory using your website's access.log as the logpath

sudo nano /etc/fail2ban/jail.d/wordpress-auth.local
[wordpress-auth]
enabled = true
port = http,https
filter = wordpress-auth
logpath = /var/log/your-website/access.log
maxretry = 5
findtime = 10m
bantime = 1h

Save and Restart Fail2ban

sudo systemctl restart fail2ban

Now verify your regex patterns directly against that specific log


sudo fail2ban-regex /var/log/your-website/access.log /etc/fail2ban/filter.d/wordpress-auth.conf

If your filter is working, you will see a positive number of matched. 0 matched means the filter is not working and needs modification(s). If you have matches, but the IP is not banned, adjust the maxretry and findtime.

If you are getting 0 matched on any of your filters, there are several steps you can take.

Check your log formats.
If no format is specified, Nginx uses the predefined combined format (with the exception of error logs):
log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

The following variables are most commonly used in log format definitions:


Predefined Error Log Format
Because the error log format is hardcoded into the Nginx source code, every error log entry consistently follows this structure:
Breakdown of Fields
Example of website error log

2026/07/09 02:03:46 [error] 21047#21047: *424 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 10.0.0.50, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "10.1.1.1"
Compare a log entry that should have matched with the failregex = in the filter(.conf)
1. Copy the log entry from logpath = from your jail into a text editor.
2. Copy the output of:
sudo cat /etc/fail2ban/filter.d/nginx-botsearch.conf
into the text editor below the log entry. Use the following document as a reference to translate the failregex = statement:


Fail2ban and ModSecurity

The combination of Fail2ban and ModSecurity provides a strong, layered defense. In this section, we will configure Fail2ban to monitor the ModSecurity audit log and block attacks before they hit the website. Next we will configure Fail2ban to monitor the Nginx error log specifically for ModSecurity: Access denied error

Build a custom Fail2ban ModSecurity filter
Create a configuration file /etc/fail2ban/filter.d/modsec.conf


Create a new jail in your /etc/fail2ban/jail.d directory using the ModSecurity audit log in the logpath

sudo nano /etc/fail2ban/jail.d/modsec.local
[modsec]
enabled = true
filter = modsec
port = http,https
banaction = ufw[blocktype=deny]
backend  = auto
journalmatch = _SYSTEMD_UNIT=nginx.service
logpath = /var/log/modsec_audit.log
logencoding = utf-8
maxretry = 3
findtime = 10m
bantime = 1d

Save and Restart Fail2ban

sudo systemctl restart fail2ban

Test your new jail


sudo fail2ban-regex /var/log/modsec_audit.log /etc/fail2ban/filter.d/modsec.conf

Build a custom Nginx ModSecurity filter
Create a configuration file at /etc/fail2ban/filter.d/nginx-modsec.conf


Create a new jail in your /etc/fail2ban/jail.d directory using the Nginx error log in the logpath

sudo nano /etc/fail2ban/jail.d/nginx-modsec.local
[nginx-modsec]
enabled  = true
port     = http,https
filter   = nginx-modsec
logpath  = /var/log/nginx/error.log
maxretry = 2
findtime = 10m
bantime  = 1d

Save(ctl-o ctl-x) and Restart Fail2ban

sudo systemctl restart fail2ban

Test your new jail


sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-modsec.conf

Conclusion

Hopefully, you have installed and configured Fail2ban and have a good understanding of how it works and how to customize fail2ban for your specific needs. The content in this post has been tested extensively in our test system. Each system is different, so you may need modifications to comply with yours. Feel free to leave a comment if you have any questions.

Introduction

Fail2Ban is a server intrusion prevention framework that actively monitors your server's log files (like SSH, FTP, or web server logs) for suspicious activity. It looks for patterns, such as multiple failed login attempts or repeated 404 errors indicating a bot is scanning the site.Once a predefined threshold is met, it automatically updates the server's firewall (like iptables, nftables or UFW) to ban the malicious IP address for a specified amount of time. Fail2ban was originally designed to block a series of unsuccessful login attempts via SSH, but unless you absolutely need password authentication, it is far more secure to use public key authentication, disable root login and disable password authentication. The package includes a variety of filters you can choose from (in /etc/fail2ban/filter.d) as well as the ability to create your own. If you have ModSecurity installed, Fail2ban can be configured to monitor the ModSecurity audit log to ban attackers at the system level.

Prerequisites:

Ubuntu 26.04 installed on a VPS or Bare-Metal host
Nginx 1.14.0 or later
User with sudo privileges
Optional: ModSecurity strongly recommended

Start the Fail2ban set up

Install Fail2ban
sudo apt install fail2ban

Start the service and make it start on boot
sudo systemctl enable --now fail2ban.service

Verify that the service is running and status is OK
sudo systemctl status fail2ban.service

Output
● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/fail2ban.service.d └─override.conf Active: active (running) since Fri 2026-07-03 17:27:39 EDT; 1 day 19h ago Invocation: ae8e5e48a08c4b96b7a4d760bf739551 Docs: man:fail2ban(1) Main PID: 1677 (fail2ban-server) Tasks: 17 (limit: 15092) Memory: 28.4M (peak: 41.7M) CPU: 8min 54.616s CGroup: /system.slice/fail2ban.service └─1677 /usr/bin/python3 /usr/bin/fail2ban-server -xf start Jul 04 00:00:00 example.com systemd[1]: Started fail2ban.service - Fail2Ban Service. Jul 04 00:00:00 example.com fail2ban-server[28237]: Server ready

Configuration Files and loading order
/etc/fail2ban/

Loading Order:
fail2ban.conf:  The primary configuration file that controls the operational behavior and global settings of the Fail2ban daemon itself.

jail.conf:  Jails defining combinations of Filters with Actions.

jjail.d/*.conf:  (in alphabetical order)

jail.d/*.local:  (in alphabetical order)

See JAIL.CONF(5) manpages for further reference

By default, fail2ban ships with a jail.conf file. It is recommended not to modify this file, as it can be overwritten in updates. Best practice is to create a jail.local file.

Create the jail.local Configuration File

Go to the fail2ban directory and copy the jail.conf to jail.local

cd /etc/fail2ban
sudo cp jail.conf jail.local

Edit the jail.local file

sudo nano /etc/fail2ban/jail.local

We are going to edit the DEFAULTS to conform with your system. As you can see, there are many areas of this file that are not applicable for your system. You can leave them there for now.

As the focus of this post is for Ubuntu and Nginx, we will be making modifications respectively. To get to a specific area of the file, you can search in nano by pressing ctl-f or ctl-w.

One of the first items to look at is the list of clients that are not subject to the fail2ban policies. This is set by the ignoreip directive. Add your own IP address or network to the list of exceptions to avoid locking yourself out. Find the line that says #ignoreip = 127.0.0.1/8 ::1 and un-comment and add your IP address (separated by spaces).

[DEFAULT]
...
ignoreip = 127.0.0.1/8 ::1
           your_ip_address
           additional IP address (optional)

[DEFAULT]
...
bantime = 1h

The "bantime" is the amount of time that a host is banned. It can be set as an integer in seconds or time abbreviation format (m - minutes, h - hours, d - days, w - weeks, mo - months, y - years). Set the default bantime to 1h.

The next 2 items work together.

[DEFAULT]
...
findtime  = 10m
maxretry = 5

"maxretry" is the number of failures within the findtime before a host gets banned.


By default, Fail2Ban uses iptables to block malicious IP addresses on Ubuntu. However, because Ubuntu uses UFW (Uncomplicated Firewall) as its standard firewall manager, it is highly recommended and common practice to configure Fail2Ban to utilize UFW for applying block rules instead.

[DEFAULT]
...
# Remove or comment
banaction = iptables-multiport
banaction_allports = iptables-allports

# Add
# Ban hosts using UFW instead of standard iptables
banaction = ufw[blocktype=deny]
banaction_allports = ufw[blocktype=deny]

If you have a Postfix mail server installed, modify the following settings to conform with your system.

[DEFAULT]
...
mta = postfix
sendername = Server Fail2Ban
sender = fail2ban@yourdomain.com
# ban & send an e-mail with whois report to the destemail.
action = %(action_mw)s

Press ctl+o to save your changes. Next we will configure Fail2ban to block Nginx specific attackers.

Configure Fail2ban for NGINX

Move down to the JAILS section and find [nginx-http-auth]. Since all the jails are disabled by default, we will have to enable it and assign the filter. The filter is the name of the .conf file in the filter.d directory without the file extension(.conf). This file contains the expressions that evaluate the line in the log.

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

This jail is designed to block failed login attempts


Next find the jail [nginx-botsearch]. Enable it and assign the filter.

This jail is designed to block web requests for selected URLs that don't exist

All of the jails can be customized for your needs. You can specify different bantime, findtime, maxretry, banaction, logpath, etc.
[nginx-botsearch]
enabled   = true
port      = http,https
filter    = nginx-botsearch
banaction = ufw[blocktype=deny]
backend   = auto
logpath   = /var/log/nginx/error.log
            /var/log/your-website/error.log
maxretry  = 3
bantime   = 1d
journalmatch = _SYSTEMD_UNIT=nginx.service
Enable and assign the filter to [nginx-bad-request] and [nginx-forbidden].

After making your edits, save(ctl-o) and exit(ctl-x) nano. Always restart the service after making edits.

sudo systemctl restart fail2ban

Test and Verify the Fail2ban Configuration

Check the status of the service. It will show error if there are any typos from your edits.

sudo systemctl status fail2ban
Output
● fail2ban.service - Fail2Ban Service Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; preset: enabled) Drop-In: /etc/systemd/system/fail2ban.service.d └─override.conf Active: active (running) since Wed 2026-07-08 14:00:25 EDT; 5h 36min ago Invocation: b10c0c51b4074f9fbddc579a3616c623 Docs: man:fail2ban(1) Main PID: 6588 (fail2ban-server) Tasks: 25 (limit: 15092) Memory: 20.7M (peak: 32.5M) CPU: 1min 47.540s CGroup: /system.slice/fail2ban.service └─6588 /usr/bin/python3 /usr/bin/fail2ban-server -xf start Jul 08 14:00:25 example.com systemd[1]: Started fail2ban.service - Fail2Ban Service. Jul 08 14:00:25 example.com fail2ban-server[6588]: Server ready

Verify that your jails are enabled.

sudo fail2ban-client status
Output
Status |- Number of jail: 5 `- Jail list: nginx-bad-request, nginx-botsearch, nginx-forbidden, nginx-http-auth sshd

To check the status of a specific jail:

sudo fail2ban-client status nginx-botsearch
Output
Status for the jail: nginx-botsearch |- Filter | |- Currently failed: 0 | |- Total failed: 0 | `- File list: /var/log/nginx/error.log `- Actions |- Currently banned: 1 |- Total banned: 1 `- Banned IP list: 123.456.789.100

Note: The fail2ban log file /var/log/fail2ban.log will show all matches and bans. If your jail is active but failing to ban offending actors, look through your server logs specified in the logpath = in your jails to locate where your system's errors are printing. You can verify your regex patterns directly against that specific log with the fail2ban-regex command.
Syntax:  sudo fail2ban-regex [full-path-to-log-file] [full path to filter file]

The following command reads the Nginx error log for patterns matching the nginx-botsearch [definition]


sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-botsearch.conf

 Note: The default Fail2ban nginx-http-auth jail will not work for WordPress wp-admin because WordPress handles its own login authentication at the application level and natively returns an HTTP 200 status code even for failed login attempts.
The nginx-http-auth filter only scans Nginx error logs for 401 Unauthorized errors generated by Nginx's built-in Basic HTTP Authentication (auth_basic). Because WordPress processes logins via wp-login.php without notifying Nginx of a failure, those failures are logged in the Nginx access log as HTTP 200 OK.

If you are hosting a WordPress website and you want to track native WordPress login screen attempts you must instruct Fail2ban to read the website access.log for POST requests targeting the login scripts.

Managing Fail2ban via the /etc/fail2ban/jail.d/ directory is the recommended approach for organizing custom jail configurations. It prevents package updates from overwriting your settings, keeps jails modular, and if it has a .local extension it is parsed in alphabetical order directly after the main /etc/fail2ban/jail.local so your custom jail will override the defaults in jail.local

Step 1: Build a custom WordPress login filter

Create a configuration file at /etc/fail2ban/filter.d/wordpress-auth.conf


Step 2: Create a new jail in your /etc/fail2ban/jail.d directory using your website's access.log as the logpath

sudo nano /etc/fail2ban/jail.d/wordpress-auth.local
[wordpress-auth]
enabled = true
port = http,https
filter = wordpress-auth
logpath = /var/log/your-website/access.log
maxretry = 5
findtime = 10m
bantime = 1h

Save and Restart Fail2ban

sudo systemctl restart fail2ban

Now verify your regex patterns directly against that specific log


sudo fail2ban-regex /var/log/your-website/access.log /etc/fail2ban/filter.d/wordpress-auth.conf

If your filter is working, you will see a positive number of matched. 0 matched means the filter is not working and needs modification(s). If you have matches, but the IP is not banned, adjust the maxretry and findtime.

If you are getting 0 matched on any of your filters, there are several steps you can take.

Check your log formats.
If no format is specified, Nginx uses the predefined combined format (with the exception of error logs):
log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

The following variables are most commonly used in log format definitions:


Predefined Error Log Format
Because the error log format is hardcoded into the Nginx source code, every error log entry consistently follows this structure:
Breakdown of Fields
Example of website error log

2026/07/09 02:03:46 [error] 21047#21047: *424 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 10.0.0.50, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "10.1.1.1"
Compare a log entry that should have matched with the failregex = in the filter(.conf)
1. Copy the log entry from logpath = from your jail into a text editor.
2. Copy the output of:
sudo cat /etc/fail2ban/filter.d/nginx-botsearch.conf
into the text editor below the log entry. Use the following document as a reference to translate the failregex = statement:


Fail2ban and ModSecurity

The combination of Fail2ban and ModSecurity provides a strong, layered defense. In this section, we will configure Fail2ban to monitor the ModSecurity audit log and block attacks before they hit the website. Next we will configure Fail2ban to monitor the Nginx error log specifically for ModSecurity: Access denied error

Build a custom Fail2ban ModSecurity filter
Create a configuration file /etc/fail2ban/filter.d/modsec.conf


Create a new jail in your /etc/fail2ban/jail.d directory using the ModSecurity audit log in the logpath

sudo nano /etc/fail2ban/jail.d/modsec.local
[modsec]
enabled = true
filter = modsec
port = http,https
banaction = ufw[blocktype=deny]
backend  = auto
journalmatch = _SYSTEMD_UNIT=nginx.service
logpath = /var/log/modsec_audit.log
logencoding = utf-8
maxretry = 3
findtime = 10m
bantime = 1d

Save and Restart Fail2ban

sudo systemctl restart fail2ban

Test your new jail


sudo fail2ban-regex /var/log/modsec_audit.log /etc/fail2ban/filter.d/modsec.conf

Build a custom Nginx ModSecurity filter
Create a configuration file at /etc/fail2ban/filter.d/nginx-modsec.conf


Create a new jail in your /etc/fail2ban/jail.d directory using the Nginx error log in the logpath

sudo nano /etc/fail2ban/jail.d/nginx-modsec.local
[nginx-modsec]
enabled  = true
port     = http,https
filter   = nginx-modsec
logpath  = /var/log/nginx/error.log
maxretry = 2
findtime = 10m
bantime  = 1d

Save(ctl-o ctl-x) and Restart Fail2ban

sudo systemctl restart fail2ban

Test your new jail


sudo fail2ban-regex /var/log/nginx/error.log /etc/fail2ban/filter.d/nginx-modsec.conf

Conclusion

Hopefully, you have installed and configured Fail2ban and have a good understanding of how it works and how to customize fail2ban for your specific needs. The content in this post has been tested extensively in our test system. Each system is different, so you may need modifications to comply with yours. Feel free to leave a comment if you have any questions.

Leave A Comment