Setting Up Fail2Ban on Ubuntu: The Rules That Actually Matter for Web Servers
Every publicly accessible server receives constant automated probing. Bots scan for open SSH ports, attempt weak credentials, and hammer web application login forms with credential stuffing lists. Without a tool that actively monitors and blocks this traffic, your server wastes resources processing requests from attackers and carries unnecessary risk of compromise through brute force or credential guessing.
Fail2Ban watches your log files and automatically blocks IP addresses that exhibit suspicious behaviour. It is lightweight, configurable, and effective when set up correctly. This guide covers how it works, which configurations matter for web servers, what many tutorials get wrong, and how to avoid locking yourself out of your own machine.
How Fail2Ban Works
Fail2Ban operates on three core concepts: filters, actions, and jails. Filters are regular expressions that match abusive patterns in log entries. Actions are the steps Fail2Ban takes when a filter matches, typically creating an iptables firewall rule to block traffic from the offending IP. Jails combine a filter with conditions specifying how many matches within what time window triggers the action.
When a jail's threshold is exceeded, Fail2Ban executes the action, which blocks the IP for a configurable ban period. When the ban expires, the firewall rule is removed automatically. This loop runs continuously as a background service, keeping watch over your server's log files around the clock.
The default installation covers SSH out of the box. Everything else requires explicit jail configuration, including web server logins, PHP application brute force protection, malicious URL request monitoring, and Apache or Nginx-specific attack patterns. If you are setting this up alongside other server hardening measures, a hardening checklist for production Ubuntu servers can help you establish a solid foundation for your overall security posture.
Installing Fail2Ban on Ubuntu
Installing Fail2Ban on Ubuntu is straightforward. Update your package lists and install the package using apt:
sudo apt update
sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo systemctl status fail2ban
The main configuration lives in /etc/fail2ban/jail.conf. Never edit this file directly because it gets overwritten on package updates. Create your overrides in /etc/fail2ban/jail.local instead. Any setting defined in jail.local takes precedence over the defaults in jail.conf, which makes it the safe place for your custom configuration.
After installation, verify the service is running correctly before proceeding with configuration. The status command should show the service as active and running without errors.
The Default Section: What Each Setting Controls
The [DEFAULT] section in jail.local applies to all jails unless you override it on a per-jail basis. Here is what matters in that section:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
destemail = admin@yourdomain.com
sender = fail2ban@yourdomain.com
action = %(action_mwl)s
bantime sets how many seconds an IP stays blocked after triggering a ban. findtime defines the monitoring window in which failures must occur to trigger a ban. maxretry sets how many failures are allowed within the findtime window before the action is triggered. The action_mwl setting bans the IP and sends an email notification with whois information and the relevant log entries that caused the ban.
The critical pair is bantime and findtime. If your ban time is shorter than the monitoring window, an attacker with enough IP addresses can cycle through them faster than bans expire. For SSH, a short findtime of 600 seconds with a longer bantime of 3600 seconds works well. For web application brute force where attacks may persist longer, consider a findtime of 1800 seconds and a bantime of 7200 seconds.
You can change the action to action_ for simple banning without email notifications, or action_xarf if you have a XARF abuse reporting system configured for sending reports to upstream providers.
SSH Jail: Real Configuration That Blocks Attacks
SSH is the most targeted service on any server with a public IP address. Automated bots hammer port 22 constantly. Even if you only use key-based authentication, blocking this noise reduces log volume and frees server resources. A secure SSH configuration on Ubuntu covers additional hardening steps that work well alongside Fail2Ban.
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
backend = systemd
The built-in sshd filter already handles the standard SSH failed login format in auth.log. You can verify it matches correctly before going live using the fail2ban-regex tool against your own authentication log:
sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf
Look for lines showing matched text and a non-zero hit count in the output. If the filter returns zero matches against your auth.log when you know there have been failed login attempts, the regex may not match your SSH version's log format and will need adjustment.
One setting many tutorials skip is backend = systemd. Fail2Ban defaults to auto-detecting the log backend. On systems using systemd's journal, setting the backend explicitly to systemd ensures Fail2Ban reads from the journal rather than a flat file that may not exist or may rotate in unexpected ways on your system.
Apache and Nginx Jails
For web servers, you have several jail options depending on what you are protecting. Understanding your web server configuration and the logs it generates is essential before enabling these jails.
Apache HTTP Basic Auth Protection
If you use HTTP Basic Auth for any part of your site such as admin panels, staging environments, or internal tools, enable the apache-http jail:
[apache-http-auth]
enabled = true
port = http,https
filter = apache-http-auth
logpath = /var/log/apache2/error.log
maxretry = 3
bantime = 3600
findtime = 600
This jail monitors the Apache error log for authentication failures and blocks IPs that repeatedly fail to authenticate against HTTP Basic Auth protected resources.
Nginx HTTP Basic Auth Protection
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
findtime = 600
The Nginx version works the same way but monitors the Nginx error log for authentication failures against protected locations.
Nginx Bad Bots Detection
Fail2Ban ships with a nginx-botsearch filter that matches common malicious URL patterns including admin panel access attempts, configuration file access probes, SQL injection attempts, and PHPMyAdmin scanning:
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 7200
findtime = 600
The nginx-botsearch filter is aggressive by default. It matches any request for paths like /wp-admin, /admin/config.php, /.env, and /phpmyadmin. If you host legitimate WordPress at /wp-admin, these requests from your own users would trigger bans. Only enable this jail if your legitimate traffic does not include these paths, or adjust the ignoreregex to exclude your own IP addresses from detection.
Apache ModSecurity Integration
If you run ModSecurity with Apache, the apache-modsecurity jail monitors the ModSecurity audit log for suspicious activity that matches your configured rules:
[apache-modsecurity]
enabled = true
port = http,https
filter = apache-modsecurity
logpath = /var/log/apache2/modsec_audit.log
maxretry = 5
bantime = 3600
findtime = 600
Securing Apache HTTPd with proper firewall rules and configuration hardening complements Fail2Ban protection and creates a more robust server setup.
Custom Filters for PHP Applications
Most PHP applications log failed login attempts in their own files rather than the web server error log. To protect a custom PHP application, you need a custom filter. First, find where the application writes authentication failure events. Common locations include /var/www/myapp/var/log/auth.log, /var/www/myapp/storage/logs/auth.log, or the system syslog.
Look at the actual log format your application produces. Your filter's failregex must match the exact format of your application's log entries. Here is an example log line from a hypothetical application:
2026-05-22 14:23:11 ERROR 192.168.1.50 Failed login attempt for user: admin
The corresponding filter would be:
[Definition]
failregex = ^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ERROR <HOST> Failed login attempt for user: admin$
ignoreregex =
Save this as /etc/fail2ban/filter.d/myapp-auth.conf. Test it against real log data before enabling the jail:
sudo fail2ban-regex /var/www/myapp/var/log/auth.log /etc/fail2ban/filter.d/myapp-auth.conf
The output shows how many lines matched and which regex groups captured the IP address. If you see zero matches but know there are failed logins in the file, the log format does not match your regex. Adjust the pattern until it captures the real entries from your application.
Once the test shows matches, add the jail to your jail.local configuration:
[myapp-auth]
enabled = true
port = http,https
filter = myapp-auth
logpath = /var/www/myapp/var/log/auth.log
maxretry = 5
bantime = 3600
findtime = 600
WordPress and Common CMS Protection
WordPress logs failed login attempts to /wp-login.php in the Apache access log with a 200 status code and a query string containing log and pwd. A simple filter for WordPress admin protection requires both a jail entry and a custom filter file.
Jail configuration in jail.local:
[wordpress]
enabled = true
port = http,https
filter = wordpress
logpath = /var/log/apache2/access.log
maxretry = 5
bantime = 3600
findtime = 600
Filter file at /etc/fail2ban/filter.d/wordpress.conf:
[Definition]
failregex = ^<HOST> - .* "POST /wp-login.php.*" 200
ignoreregex =
Many WordPress sites also have the XML-RPC interface enabled, which is frequently abused for brute force and pingback attacks. To monitor and block XML-RPC abuse, add this jail to your configuration:
[wordpress-xmlrpc]
enabled = true
port = http,https
filter = wordpress-xmlrpc
logpath = /var/log/apache2/access.log
maxretry = 3
bantime = 7200
findtime = 600
Filter file at /etc/fail2ban/filter.d/wordpress-xmlrpc.conf:
[Definition]
failregex = ^<HOST> - .* "POST /xmlrpc.php.*"
ignoreregex =
Recursive Scan Detection
A useful configuration for high-traffic servers is monitoring for recursive scan patterns. This means an IP hitting many different non-existent URLs in rapid succession, which often indicates a vulnerability scanner or a compromised host being used for reconnaissance.
[apache-noscript]
enabled = true
port = http,https
filter = apache-noscript
logpath = /var/log/apache2/access.log
maxretry = 10
bantime = 7200
findtime = 600
This jail blocks IPs that trigger a high number of 404 errors for missing scripts including .php, .pl, and .cgi files. The threshold of 10 is adjustable based on your traffic patterns. Lower it on low-traffic sites and raise it on sites with many legitimate varied URLs. The SSH and HTTP protection setup guide covers additional jail configurations for common attack patterns you may encounter.
Whitelist Configuration
The ignoreip setting prevents bans on IP addresses you control. Add it to the [DEFAULT] section of your jail.local:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.50 10.0.0.0/8
Specify individual IPs or CIDR ranges as needed. Do not whitelist large ranges without good reason. Whitelisting 10.0.0.0/8 exempts millions of IP addresses from all bans, which defeats the purpose of having protection in place.
If you manage the server via a specific VPN or jump host, add that IP explicitly to the whitelist. If your office uses a fixed IP address, add it. If you rely on dynamic DNS for remote access, you cannot safely whitelist it because the IP can change. Instead, set a higher maxretry for your services or implement certificate-based access controls that do not depend on IP allowlisting.
Persistent Bans with SQLite
Fail2Ban stores active bans in memory by default. If the service restarts, all ban state is lost and repeat offenders can reconnect immediately. To persist bans across restarts, enable the database backend in your jail.local:
[DEFAULT]
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
With the database backend enabled, you can also implement progressively longer bans for repeat offenders using the recidive jail. This special jail monitors the Fail2Ban log itself for IPs that have been banned repeatedly within a longer time window:
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
action = iptables-allports
maxretry = 2
bantime = 604800
findtime = 86400
The recidive jail triggers a seven-day block for IPs that trigger bans repeatedly. An IP that gets banned three times in one day clearly is not giving up and deserves a longer block to prevent continued probing of your server.
Monitoring and Verification
Fail2Ban ships with a client tool for inspecting active state and current configuration. These commands help you understand what is happening on your server:
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client get sshd bantime
sudo fail2ban-client get sshd findtime
The first command lists all jails and their current ban counts. The second shows detailed status for the SSH jail specifically. The remaining commands retrieve current settings for the SSH jail, which is useful for verifying your configuration is loaded correctly.
Active bans are stored in the iptables filter table. You can also see them directly with iptables:
sudo iptables -L f2b-sshd -n --line-numbers
Log output goes to /var/log/fail2ban.log. Review it periodically to understand what is being blocked. If you see unusual patterns such as bans from IPs in a specific geographic range or attacks targeting a specific application endpoint you did not know was exposed, adjust your jail configurations accordingly.
Set up a monitoring check that alerts you if the fail2ban service stops. A simple systemd timer or Monit check that runs sudo fail2ban-client ping and alerts on failure is sufficient. If Fail2Ban is not running, your server is unprotected against the automated attacks that hit continuously.
Common Mistakes and How to Avoid Them
Setting maxretry = 1 catches many people out. If your office has five people behind a NAT gateway, one person triggering a false positive locks out all five simultaneously. Set maxretry high enough that a single user's accidental mistypes do not cause collateral damage to colleagues.
Enabling the nginx-botsearch jail without adjusting for your actual traffic catches another common mistake. If your site legitimately serves /admin or /wp-admin to users, enabling that jail as-is will ban your own users and cause support issues. Either exclude your office IP range from ignoreip, or adjust the jail's findtime and maxretry so that normal use does not trigger blocks.
Disabling fail2ban because it caused operational friction is the wrong response to self-bans. The solution to accidental self-locks is not to remove the protection. It is to tune the thresholds and add your IP to the ignoreip list. The protection is valuable precisely because it blocks malicious traffic aggressively.
Configuring ban times that are too short to matter catches beginners. A one-minute ban does not stop a determined attacker because they reconnect as soon as the block lifts. For anything other than initial testing, ban times of at least an hour are more effective at disrupting automated attack campaigns.
What Fail2Ban Does Not Cover
Fail2Ban is a server-local tool. It can only act on what it sees in your logs. It cannot block volumetric DDoS attacks that would overwhelm your server before log analysis can keep pace. It cannot protect against attacks that come from a rotating pool of thousands of IP addresses faster than you can ban them.
For those scenarios, a cloud WAF or network-layer DDoS mitigation service is required. Fail2Ban handles the day-to-day abuse including SSH brute force, application login brute force, vulnerability scanning from individual IPs, and coordinated manual attacks from a limited number of sources. It is the right tool for the threats that hit most servers continuously, and it does that job well when configured appropriately.
For web applications under active attack, also consider application-level controls. CAPTCHA on login forms after failed attempts, rate limiting at the application layer, and multi-factor authentication all raise the bar for attackers. Layering these controls with Fail2Ban creates a more robust defence in depth strategy that does not rely on any single security measure.
Building a Complete Security Setup
Fail2Ban works best when it is part of a broader security strategy. Server hardening after installation should include multiple layers of protection that complement each other. SSH key authentication, automatic security updates, firewall configuration, and regular log review all contribute to a more secure server environment.
The Fail2Ban configuration you choose should reflect your actual traffic patterns and the services you run. A generic configuration copied from a tutorial may not match your environment. Take time to understand what each jail does and whether it applies to your setup before enabling it.
Test your filters before enabling jails in production. Use fail2ban-regex against real log data to verify that your patterns match what you expect. A filter that returns zero matches against real attack data provides no protection regardless of how well it looks on paper. Regular testing ensures your protection remains effective as your server configuration evolves.
Maintaining Your Fail2Ban Installation
Monitor your Fail2Ban installation over time. Check the logs, review ban patterns, and adjust thresholds as your traffic evolves. A configuration that works well today may need tuning as your application grows or as new attack patterns emerge targeting your services.
When you update your server or application, verify that Fail2Ban filters still match your log formats correctly. Log format changes can break filters silently, leaving you unprotected without any warning. Regular testing with fail2ban-regex against recent logs catches these issues before they become problems.
Keep your Fail2Ban package updated through your system package manager. Security improvements and new jail definitions are included in updates. Running an outdated version may leave you without protection against newer attack techniques that have been identified and addressed in more recent releases.
Related practical reading
These related guides can help you connect this topic with the wider website, server, security, and support decisions around it.
- SSH Config Tips That Save Hours of Time - useful background for related technology decisions
- How to Build a PHP Webhook Receiver: Complete Implementation Guide - useful background for related technology decisions
Moving Forward
A properly configured Fail2Ban installation significantly reduces the noise and risk from automated attacks on your Ubuntu server. The effort of setting it up correctly pays off in reduced log clutter, lower resource consumption from blocked malicious traffic, and fewer successful brute force attempts against your services.
If you are uncertain about your current server security posture or want someone to review your existing Fail2Ban configuration, preparing details about your server environment, the services you run, and any specific issues you have noticed will help identify what needs attention.