Every Apache server exposed to the public internet attracts automated brute-force traffic — bots hammering login pages, probing for vulnerable paths, and cycling through credential lists around the clock. Fail2Ban watches your Apache logs in real time and automatically bans any IP that trips your threshold, feeding block rules directly into your firewall before the attacker can do real damage. By the end of this guide you will have Fail2Ban installed, two Apache-specific jails active, and a tested ban-and-unban workflow you can trust in production.
Prerequisites
- Ubuntu 22.04 LTS or 20.04 LTS (steps work on Debian 11 and 12 with no changes)
- Apache 2.4 installed, running, and writing logs to
/var/log/apache2/ ufworiptablesactive — Fail2Ban writes ban rules to whichever firewall backend you have running- Root or passwordless sudo access
- Your own public IP address noted down — you will whitelist it so you cannot accidentally ban yourself during testing
Step 1 — Install Fail2Ban
Fail2Ban is in the default Ubuntu repos. Install it and confirm the service started automatically:
sudo apt update
sudo apt install fail2ban -y
sudo systemctl status fail2ban
You should see active (running). Fail2Ban ships with a default SSH jail already enabled — you are not touching that jail in this guide, just adding Apache-specific ones on top.
Step 2 — Understand the Config File Hierarchy
Fail2Ban separates its configuration into two layers so that package upgrades never overwrite your customisations:
/etc/fail2ban/jail.confand/etc/fail2ban/filter.d/.conf— shipped by the package, do not edit these directly/etc/fail2ban/jail.localand/etc/fail2ban/filter.d/.local— your overrides, created by you, never touched by upgrades
All your work in this guide goes into .local files. Values in a .local file take precedence over the matching .conf file when Fail2Ban loads.
Step 3 — Create Your Main jail.local File
Create jail.local and open it in your editor:
sudo nano /etc/fail2ban/jail.local
Paste in the following. Replace YOUR.IP.ADDRESS.HERE with your actual public IP — this is the whitelist that stops you from banning yourself during testing:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 YOUR.IP.ADDRESS.HERE
bantime = 1h
findtime = 10m
maxretry = 5
backend = auto
banaction = ufw
[apache-auth]
enabled = true
port = http,https
filter = apache-auth
logpath = /var/log/apache2/error.log
maxretry = 5
bantime = 1h
[apache-badbots]
enabled = true
port = http,https
filter = apache-badbots
logpath = /var/log/apache2/access.log
maxretry = 2
bantime = 24h
A quick breakdown of the key values:
- bantime — how long a banned IP stays blocked. Start with
1hand raise it once you are confident in the setup. - findtime — the sliding window Fail2Ban looks back over when counting failures.
- maxretry — number of failures inside
findtimebefore a ban is issued. - banaction = ufw — tells Fail2Ban to write bans as
ufwrules. If you are using plainiptableswithoutufw, change this toiptables-multiport.
Gotcha: If your server sits behind a load balancer or a CDN that terminates connections, Apache logs the proxy IP rather than the real visitor IP. In that scenario Fail2Ban will ban your proxy, not the attacker. Fix this first by configuring
mod_remoteipin Apache to log the real client IP before enabling these jails.
Step 4 — Check the Built-In Apache Filters
Fail2Ban ships with the two filters referenced in the jails above. Confirm they exist before restarting:
ls /etc/fail2ban/filter.d/ | grep apache
You should see at minimum apache-auth.conf and apache-badbots.conf in the output. Take a moment to read what each one matches:
cat /etc/fail2ban/filter.d/apache-auth.conf
cat /etc/fail2ban/filter.d/apache-badbots.conf
apache-auth matches HTTP 401 authentication failure lines in your error log. apache-badbots matches known malicious user-agent strings in your access log. Both use standard regular expressions that you can extend in a .local override if you need to catch additional patterns.
Step 5 — Restart Fail2Ban and Confirm the Jails Are Active
Restart the service to load your new jail.local:
sudo systemctl restart fail2ban
Then check that both Apache jails show as active:
sudo fail2ban-client status
The output should list apache-auth and apache-badbots alongside the default sshd jail. Drill into one jail to see its current state:
sudo fail2ban-client status apache-auth
You will see the currently banned IP count, total failures, and the filter being used. At this point the count will be zero unless your logs already have matching failures — which is common on a server that has been running for a while.
Step 6 — Test a Filter Against Your Logs Without Banning Anyone
Before trusting the setup in production, use fail2ban-regex to dry-run a filter against your actual log file. This tells you exactly how many lines the filter would have matched without issuing a single ban:
sudo fail2ban-regex /var/log/apache2/error.log /etc/fail2ban/filter.d/apache-auth.confsudo fail2ban-regex /var/log/apache2/access.log /etc/fail2ban/filter.d/apache-badbots.conf
The output shows matched lines, ignored lines, and the matched IP addresses. If the match count is zero on a busy server, the log format may differ from what the filter expects — compare the regex in the filter file against a raw log line and adjust a .local override accordingly.
Step 7 — Manually Ban and Unban an IP to Test the Firewall Integration
Use a spare IP you control — or a known safe test IP — to verify that banning actually writes a rule to your firewall and that unbanning removes it cleanly. Never test with an IP you depend on for server access.
Issue a manual ban:
sudo fail2ban-client set apache-auth banip 203.0.113.99
Confirm the firewall rule was created:
sudo ufw status numbered
You should see a DENY rule for 203.0.113.99 on ports 80 and 443. Now unban it:
sudo fail2ban-client set apache-auth unbanip 203.0.113.99
Run sudo ufw status numbered again and confirm the rule is gone. If the ban appeared and disappeared as expected, the full pipeline — log watcher to ban action to firewall — is working correctly.
Note: The IP address
203.0.113.99is from the TEST-NET-3 documentation range (RFC 5737) and is safe to use in examples — it is not routable on the public internet.
Step 8 — Monitor Bans in Ongoing Operation
Once the jails are live, these are the commands you will reach for most often:
See all currently banned IPs across every jail:
sudo fail2ban-client status apache-auth
sudo fail2ban-client status apache-badbots
Watch the Fail2Ban log in real time to see bans as they happen:
sudo tail -f /var/log/fail2ban.log
Unban a legitimate IP that was caught by mistake:
sudo fail2ban-client set apache-auth unbanip OFFENDING.IP.HERE
If a trusted IP keeps triggering bans, add it permanently to the ignoreip line in jail.local and restart the service.
Wrap-up
Fail2Ban is now watching both your Apache error log and access log, banning repeat offenders automatically and writing those bans straight into your firewall. The two jails you enabled — apache-auth for authentication failures and apache-badbots for known malicious agents — cover the most common automated attack patterns against an Apache server. From here, consider adding the apache-noscript and apache-overflows jails from the built-in filter library, tuning bantime upward as you build confidence, or exploring incremental ban times using Fail2Ban's bantime.increment feature to make repeat offenders wait exponentially longer between retries.