If you have ever looked at an unprotected server log, you already know the truth: bots start hammering SSH the moment the box comes online. I have watched fresh VPS instances receive thousands of failed root logins within minutes of being deployed. Setting up fail2ban to block brute-force attacks on SSH and web services turns that constant background noise into something you can ignore.
This is the guide I wish I had when I was learning fail2ban. We will walk through installation, configuration, the SSH jail, Nginx and Apache jails, testing with fail2ban-regex, the recidive jail, troubleshooting, and best practices I have picked up after running fail2ban across dozens of production servers.
Table of Contents
What Is Fail2ban and How Does It Work?
Fail2ban is a log-parsing intrusion prevention tool for Linux. It reads log files, applies regex patterns to identify malicious behavior, and uses the system firewall to ban offending IP addresses for a set period of time. The whole idea is automation: instead of manually blocking IPs, fail2ban watches logs and reacts.
Fail2ban itself is not a firewall. It is a control layer that sits on top of iptables, nftables, or ufw. When a pattern matches too many times within a defined window, fail2ban tells the firewall to drop traffic from that IP.
The Three Building Blocks: Jails, Filters, and Actions
The easiest way to understand fail2ban is to learn its three main concepts:
Jail: defines what log to watch, which filter to apply, how many failures trigger a ban, and how long the ban lasts.
Filter: a set of regular expressions (failregex) used to identify malicious log lines.
Action: what to do when the threshold is hit, typically inserting a firewall rule to drop traffic.
A jail ties a filter and an action together. For example, the sshd jail watches /var/log/auth.log, uses the sshd filter to spot failed logins, and uses an iptables action to block the IP.
What Fail2ban Protects You From
Out of the box, fail2ban ships with filters for SSH, Nginx, Apache, Postfix, Dovecot, and many more services. The most common attacks it stops include:
SSH brute-force login attempts
Repeated 401 responses on protected web paths
Scanners looking for vulnerable PHP scripts and admin panels
Bots spamming comment forms and login endpoints
Repeat offenders that just come back the moment their ban expires
Fail2ban runs as a background service, so the protection works even when you are asleep. That is why it remains one of the first tools I install on any new server.
How to Install Fail2ban on Debian and Ubuntu?
Fail2ban is in the standard repositories, so installation is straightforward. Open a terminal with root access or sudo and run the following commands.
Step 1: Update your package index and install fail2ban.
sudo apt update
sudo apt install fail2ban -yStep 2: Start and enable the fail2ban service so it survives reboots.
sudo systemctl enable fail2ban
sudo systemctl start fail2banStep 3: Verify that fail2ban is running.
sudo systemctl status fail2banOn CentOS, RHEL, or Rocky Linux, you would use dnf install fail2ban fail2ban-systemd instead. The configuration paths remain the same.
Quick Sanity Check
Once the service is up, confirm it is actively watching something. Even with no jails enabled, fail2ban should respond to the client tool.
sudo fail2ban-client pingIf the response is Server replied: pong, you have a working installation.
Understanding the Fail2ban Configuration File Structure
The fail2ban configuration lives under /etc/fail2ban. Knowing what each file does makes troubleshooting much faster. Most beginners edit the wrong file and get frustrated when their changes vanish after an update.
The Main Configuration Directory
Here is what you will find after installation:
/etc/fail2ban/fail2ban.conf: daemon-wide settings like log target, socket, and pidfile./etc/fail2ban/jail.conf: the default jail definitions shipped with fail2ban. Do not edit this file directly./etc/fail2ban/jail.local: your overrides. Create this file manually./etc/fail2ban/jail.d/: directory for drop-in jail snippets./etc/fail2ban/filter.d/: prebuilt and custom regex filters./etc/fail2ban/action.d/: prebuilt and custom ban actions./etc/fail2ban/fail2ban.d/: directory for daemon-level drop-ins.
Why You Should Never Edit jail.conf
Fail2ban upgrades can overwrite jail.conf. Any custom changes you make there will be lost. Instead, fail2ban reads jail.local first and merges those settings on top of the defaults. We will create jail.local next.
Filters and Actions Explained
Filters in filter.d are files containing failregex lines. Actions in action.d define what happens on ban and unban, including sending email or calling a webhook. We will look at both later when creating custom rules.
Creating and Tuning Your jail.local File
This is the heart of fail2ban configuration. We will build a jail.local file that overrides defaults and enables the jails you actually need. I have used variations of this config on Ubuntu 22.04, Debian 12, and similar systems.
Step 1: Create the File
sudo nano /etc/fail2ban/jail.localAdd a [DEFAULT] section first. Anything here applies to every jail unless a jail overrides it.
Step 2: Define Default Behavior
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 192.0.2.0/24
bantime = 1h
findtime = 10m
maxretry = 5Here is what each setting means in plain English.
ignoreip: comma-separated list of IPs or CIDR ranges that are never banned. Always include your own IP.
bantime: how long a ban lasts.
1hmeans one hour,10mten minutes,1done day.findtime: the window of time in which failures are counted.
maxretry: how many failures inside
findtimetrigger a ban.
Step 3: Recommended Production Values
The defaults in jail.conf are conservative. For internet-exposed servers I usually tighten them.
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 YOUR.TRUSTED.IP.ADDRESS
bantime = 24h
findtime = 10m
maxretry = 3
backend = polling
destemail = [email protected]
sender = [email protected]
mta = sendmail
action = %(action_mw)s
%(action_mwl)sThe action_mw action bans the IP and emails the admin with whois info. The action_mwl action does that and also includes the offending log lines.
Step 4: Save and Restart
sudo systemctl restart fail2banAny errors will appear in /var/log/fail2ban.log. Always check that file first if something looks wrong.
Configuring the SSH Jail to Block Brute-Force Attacks
The SSH jail is the most important one for almost every server. Fail2ban ships with a solid sshd filter, so enabling the jail is just a few lines in jail.local.
Enable the Default sshd Jail
Append the following to /etc/fail2ban/jail.local.
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
findtime = 10m
bantime = 1dOn Debian and Ubuntu, %(sshd_log)s expands to /var/log/auth.log. On RHEL-family systems it expands to /var/log/secure. That is why hardcoding log paths can cause silent failures after switching distros.
Handling Non-Standard SSH Ports
If you moved SSH to port 2222 for obscurity, you need to tell fail2ban.
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = %(sshd_log)s
maxretry = 3
bantime = 1dYou can list multiple ports with commas or use port = all to monitor any port. The sshd filter inspects the message content, not just the port, so changing ports rarely breaks detection.
Aggressive Settings for Public-Facing Servers
For anything internet-facing I drop maxretry to 2 and extend bantime to one week. Bots that try common usernames rarely ever give up after one short ban, but they almost always move on after a week.
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = %(sshd_log)s
maxretry = 2
findtime = 10m
bantime = 1wSetting Up Nginx Jails for Web Protection
SSH is only part of the story. Web services get hammered by scanners too. Fail2ban comes with several Nginx filters you can enable right away.
Enable the nginx-http-auth Jail
Use this jail to block IPs that hit 401 responses repeatedly. It is great for protecting /admin, /wp-login.php, or any HTTP-basic-authenticated endpoint.
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 3
findtime = 10m
bantime = 1hUse the Prebuilt nginx-botsearch Jail
The nginx-botsearch filter catches scanners looking for /wp-admin, /phpmyadmin, /xmlrpc.php, and other common targets.
[nginx-botsearch]
enabled = true
filter = nginx-botsearch
port = http,https
logpath = /var/log/nginx/access.log
maxretry = 2
bantime = 1dEnable nginx-limit-req for Application-Level Abuse
If you already use Nginx limit_req, fail2ban can act on the 429 responses. This is a quick way to push the abuse boundary out to the firewall too.
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 2
findtime = 10m
bantime = 1hVerify That Nginx Log Paths Match
The most common reason the Nginx jails fail silently is a wrong log path. Check your Nginx config with sudo nginx -T | grep access_log and use the same paths in jail.local.
Setting Up Apache Jails (apache-auth, apache-badbots)
If you run Apache instead of Nginx, the equivalent jails are equally important. The filter names are similar but the log paths differ.
Enable the apache-auth Jail
[apache-auth]
enabled = true
filter = apache-auth
port = http,https
logpath = /var/log/apache2/error.log
maxretry = 3
findtime = 10m
bantime = 1hEnable the apache-badbots Jail
[apache-badbots]
enabled = true
filter = apache-badbots
port = http,https
logpath = /var/log/apache2/access.log
bantime = 1d
maxretry = 1The default apache-badbots filter is aggressive. I keep maxretry at 1 and put bantime at a full day. Almost nothing legitimate matches these patterns.
Handle Virtual Host Log Files
Custom Apache virtual hosts often write to their own log files inside /var/log/apache2/. If your apache-auth jail is silent while attacks are clearly happening, your real access logs are probably elsewhere.
You can point the jail at multiple log files using globs.
logpath = /var/log/apache2/*error.logThis works for Nginx too. Globs are expanded at startup and reread every couple of minutes.
Testing Filters With fail2ban-regex Before Going Live
This is the single most underrated feature of fail2ban. The fail2ban-regex tool lets you test filters against real log lines before the jail ever bans anyone. I run this every time I write a custom filter.
What fail2ban-regex Does
It loads a filter file, runs the regex against a log file (or stdin), and prints which lines matched. This lets you catch false positives before they lead to banned customers or banned coworkers.
Basic Syntax
fail2ban-regex /path/to/log /etc/fail2ban/filter.d/sshd.confThe output shows the count of matches and the IPs detected. If the count is 0 and you know there should be matches, your filter or logpath is wrong.
Test Using a Snippet Instead of a File
You can pipe a few log lines directly into fail2ban-regex. That is great when iterating on a new filter.
tail -n 50 /var/log/auth.log | fail2ban-regex - /etc/fail2ban/filter.d/sshd.confTest Custom Filters Quickly
Suppose you wrote a custom filter at /etc/fail2ban/filter.d/myapp.conf. Test it against today’s log.
sudo fail2ban-regex /var/log/myapp/access.log /etc/fail2ban/filter.d/myapp.confLook for the line that says Lines: ... matched, ... missed. If missed is high, your failregex is wrong. Iterate until both numbers reflect what you expect.
Adding the Recidive Jail for Repeat Offenders
The recidive jail is the upgrade path that most beginner guides skip. It watches fail2ban’s own log for offenders who got banned by another jail, came back, got banned again, and then came back a third time. Those are the bots worth blocking hard.
Enable Recidive in jail.local
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 5
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 4wWhat These Settings Do
With bantime.increment enabled, every new offense increases the ban length. After five offenses the offender is blocked for 1 week, then 2 weeks, then 4 weeks, capped at bantime.maxtime. This is exactly what you want for abusers.
Why Recidive Matters
Most attackers rotate through botnets. Short bans do little against them. The recidive jail makes repeat offenders effectively disappear for a month at a time. I have seen single IPs banned for the full maxtime on long-running production servers.
Resetting Recidive Bans
If you ever need to lift a recidive ban early, you can use the regular unban flow.
sudo fail2ban-client set recidive unbanip 198.51.100.42Troubleshooting Common Fail2ban Issues
Most fail2ban problems fall into a few predictable buckets. I have hit each of these at some point on a real server, so here is the playbook I follow.
Bans Not Being Applied
If the status shows unban count climbing but no banned IPs ever appear, the firewall backend is probably broken. Check the fail2ban log first.
sudo tail -n 100 /var/log/fail2ban.logLook for errors mentioning iptables, nftables, or Action. On Ubuntu 22.04 and newer, fail2ban defaults to nftables. If you also run ufw, they can clash. Switch to polling-only.
[DEFAULT]
backend = pollingLog Path Mismatches
If your jail never matches anything, the log path is wrong. Confirm with the file actually being written.
ls -la /var/log/auth.log /var/log/secure 2>/dev/null
sudo journalctl -u sshd --since "1 hour ago"For modern systemd systems that journal everything, point the jail at the journal.
[sshd]
enabled = true
backend = systemd
journalmatch = _SYSTEMD_UNIT=sshd.service
maxretry = 3
bantime = 1dConflicts With ufw or firewalld
Running ufw, firewalld, or a cloud firewall like AWS security groups alongside fail2ban can produce strange behavior. The fix is to choose one firewall to manage fail2ban and let the others stay out of the way.
I recommend the ufw action when both are in use. It plays nicely with ufw’s existing rules.
[sshd]
action = ufw
%(action_mwl)sVerifying With fail2ban-client
The fail2ban-client tool is your inspection lens. Use it every time you change a config.
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client get sshd banip
sudo fail2ban-client set sshd unbanip 198.51.100.10If a jail is missing from status output, fail2ban failed to load it. Check the log for syntax errors first.
Best Practices and Security Hardening Tips
Fail2ban is not a complete security strategy. It is one layer in a defense-in-depth model. Here is how I use it alongside other controls.
Combine Fail2ban With Key-Based SSH
Disable password authentication entirely on production servers. Once SSH keys are in place, no amount of brute-forcing will succeed, and fail2ban stops automated attacks long before they hit your sshd limits.
# /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yesAlways Whitelist Your Own IP
There is no faster way to get locked out of your own server than mistyping your IP into ignoreip. Use CIDR notation and keep the list current.
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24Enable the Database Backend for Persistent Bans
Fail2ban’s default SQLite backend survives restarts. Add this to /etc/fail2ban/fail2ban.local if your distro supports it.
[Definition]
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
dbpurgeage = 1wReview Configuration Regularly
Services change. Logs change. Filters that worked six months ago may no longer match the new SSH version’s log format. Run a quick fail2ban-regex against recent logs every few months. It takes five minutes and catches silent breakage before it matters.
Frequently Asked Questions
How do I install and configure fail2ban for SSH and Nginx?
Install with sudo apt install fail2ban, then create /etc/fail2ban/jail.local with [DEFAULT] settings (ignoreip, bantime, findtime, maxretry) and enable the [sshd] and [nginx-http-auth] jails. Restart fail2ban with sudo systemctl restart fail2ban and verify with sudo fail2ban-client status sshd.
How do I unban an IP from fail2ban?
Run sudo fail2ban-client set u0026lt;jailu0026gt; unbanip u0026lt;IPu0026gt;. Replace u0026lt;jailu0026gt; with the jail that currently holds the ban (for example sshd) and u0026lt;IPu0026gt; with the address you want to release. Confirm with sudo fail2ban-client get u0026lt;jailu0026gt; banip.
How do I test a fail2ban filter before going live?
Use fail2ban-regex. For example, sudo fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf. The output shows matched and missed lines. If missed count is high, adjust failregex in your filter and test again.
What is the recidive jail and why should I enable it?
The recidive jail bans IP addresses that keep coming back after prior bans. Enable it in /etc/fail2ban/jail.local by adding a [recidive] section that reads /var/log/fail2ban.log. With bantime.increment set to true and bantime.factor at 2, repeat offenders get exponentially longer bans.
What is fail2ban and why is it important for server security?
Fail2ban is an intrusion prevention tool that scans log files for malicious patterns and bans offending IPs via the system firewall. It is important because it automates protection against brute-force and bot attacks on SSH and web services, reducing manual work and exposure to automated threats.
Wrapping Up the Setup
Setting up fail2ban to block brute-force attacks on SSH and web services is one of the highest-leverage security tasks you can do for any Linux server. The tool is free, mature, and works whether you run a single VPS or a fleet of web hosts.
Start with a clean jail.local, enable the SSH and HTTP-auth jails, add the recidive jail once you trust the basics, and validate every change with fail2ban-regex. Combine that with key-based SSH and a sensible ignoreip list, and your server will quietly absorb thousands of automated attacks per day without you ever noticing.
Pick one jail to set up today, run sudo fail2ban-client status on your own box to see what is happening right now, and iterate from there. Your future self (and your login logs) will thank you.