← All posts

Tutorial Security

How to Set Up Fail2Ban to Block Brute-Force Attacks on Apache

Set up Fail2Ban on Apache to automatically detect and ban brute-force attackers using your existing access and error log

Difficulty Intermediate Time 30 min
A glowing digital shield deflecting brute-force attack requests in front of an Apache server, representing Fail2Ban protection.

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/
  • ufw or iptables active — 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.conf and /etc/fail2ban/filter.d/.conf — shipped by the package, do not edit these directly
  • /etc/fail2ban/jail.local and /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 1h and 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 findtime before a ban is issued.
  • banaction = ufw — tells Fail2Ban to write bans as ufw rules. If you are using plain iptables without ufw, change this to iptables-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_remoteip in 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.99 is 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.