Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

fail2ban custom rules

A collection of custom fail2ban filters for Apache and Nginx servers (Virtualmin, plain Apache, plain Nginx all supported). Designed for real-world attacks seen on production CakePHP, WordPress, and general web hosting environments.

Originally started as a bootstrap install script for apache-wp-login on Ubuntu 14.04 + Virtualmin (gist 18fcc234a324c858d50d0a96d5748ab4), and grew into this repo as more attack patterns were observed.

Despite the apache- prefix, all filters work unchanged with Nginx. Both servers use identical combined log format by default, and the filters match on URL patterns, not server-specific syntax. See Works with Apache and Nginx below.

How blocking works

Understanding where bans are enforced helps you choose the right approach for your threat level:

Level Mechanism Performance What catches the traffic
Application (slowest) Web server rewrite rules, .htaccess, PHP code Per-request overhead, still consumes PHP/worker Apache/Nginx reads request, routes to handler, handler rejects
fail2ban + iptables (good) Kernel netfilter INPUT chain, one rule per IP Linear scan per packet — slow with thousands of IPs Packet hits firewall before it reaches any userspace process
fail2ban + ipset (best) Kernel ipset hash lookup, O(1) Can handle millions of IPs with no slowdown Packet dropped at kernel level via hash table
Live IP feeds + ipset (proactive) Spamhaus DROP, FireHOL, AbuseIPDB via cron O(1) lookup, updated every few hours Known-bad IPs blocked before they even probe

Recommended setup

  1. fail2ban filters (this repo) detect attack patterns in logs and ban offenders
  2. ipset as the ban backend — via iptables-ipset action, so ban lookups are O(1)
  3. Live blocklists (Spamhaus/FireHOL) for proactive blocking of known-bad netblocks
  4. iptables rules apply the ipset match on the INPUT chain — kernel-level, before any web server sees the packet

See docs/ipset-blocklists.md for complete setup.

Filters

Filter What it catches Default ban
apache-wp-login Brute-force attempts on /wp-login.php 1 hit = 10h ban
apache-script-hackbots Bots probing for known vulnerable scripts (phpMyAdmin, shells, config files) — extensive list 1 hit = 10h ban
apache-script-hackbot-shorter Same as above but a shorter, lighter rule set 1 hit = 10h ban
apache-url-enumeration Bots generating thousands of deep nested URL path combinations (6+ segments returning 403/404) to enumerate routes on CakePHP/Laravel apps 5 hits/min = 24h ban
apache-probe-paths Comprehensive list of 1100+ attack/probe URLs captured from real attacks — VoIP SIP device configs (spa122.xml, Yealink, Polycom), PHP web shells, phpMyAdmin variants, CMS probes, shellshock/IFS injection, pChart2 traversal, IoT/router probes, scanner user-agents 1 hit = 10h ban
nginx-proxy-abuse Catches attempts to use your server as an open HTTP proxy (absolute URLs or CONNECT method in request line) 1 hit = 7d ban

Installation

Quick install (all filters)

sudo cp filter.d/*.conf /etc/fail2ban/filter.d/
sudo cat jail.conf >> /etc/fail2ban/jail.local
sudo fail2ban-client reload

Individual filter

Download a single filter and add the matching jail:

# Example: install just the URL enumeration filter
sudo wget -O /etc/fail2ban/filter.d/apache-url-enumeration.conf \
  https://raw.githubusercontent.com/acosonic/fail2ban_custom_rules/master/filter.d/apache-url-enumeration.conf

Then add to /etc/fail2ban/jail.local:

[apache-url-enumeration]
enabled  = true
port     = http,https
filter   = apache-url-enumeration
logpath  = /var/log/virtualmin/*access_log
maxretry = 5
findtime = 60
bantime  = 86400

[apache-probe-paths]
enabled  = true
port     = http,https
filter   = apache-probe-paths
logpath  = /var/log/virtualmin/*access_log
maxretry = 1
findtime = 36000
bantime  = 36000

[nginx-proxy-abuse]
enabled  = true
port     = http,https
filter   = nginx-proxy-abuse
logpath  = /var/log/nginx/*access.log
           /var/log/virtualmin/*access_log
maxretry = 1
findtime = 3600
bantime  = 604800

Reload:

sudo fail2ban-client reload

Works with Apache and Nginx

Despite the apache- prefix, these filters work with both Apache and Nginx out of the box. Both servers use identical combined log format by default:

1.2.3.4 - - [18/Feb/2025:08:05:54 -0800] "GET /path HTTP/1.1" 403 428 "-" "UA"

The filters match on ^<HOST>.*URL-pattern, which is log-format-agnostic. The only thing you need to change is the logpath in jail.conf.

Log paths

The default jail.conf uses Virtualmin log paths. Adjust for your setup:

Server Log path
Virtualmin /var/log/virtualmin/*access_log
Apache default /var/log/apache2/*access.log
Nginx /var/log/nginx/*access.log
Multiple /var/log/apache2/*access.log
/var/log/nginx/*access.log (one per line)
Custom Check your vhost config

Custom log formats

If you use a non-standard log format (e.g. JSON logs, custom log_format), the regex may need adjustment. The easiest approach:

  1. Test with fail2ban-regex /path/to/your.log filter.d/apache-probe-paths.conf
  2. If nothing matches, paste a sample log line into Claude / ChatGPT along with the filter and ask it to adapt the regex to your format. The filters are plain regex, easy to tweak.

Verify it works

root@host:~# fail2ban-client status apache-script-hackbots
Status for the jail: apache-script-hackbots
|- Filter
|  |- Currently failed:  0
|  |- Total failed:      264
|  `- File list:         /var/log/virtualmin/example.com_access_log
`- Actions
   |- Currently banned:  9
   |- Total banned:      10
   `- Banned IP list:    209.95.51.11 109.70.100.20 134.175.26.138 ...

Other useful commands:

# Status of all jails
sudo fail2ban-client status

# Unban an IP
sudo fail2ban-client set apache-script-hackbots unbanip 1.2.3.4

# Test a filter against a log file (dry run)
sudo fail2ban-regex /var/log/virtualmin/example.com_access_log /etc/fail2ban/filter.d/apache-url-enumeration.conf

Recommended jail.local

A complete jail.local combining these filters with standard protections:

[sshd]
enabled  = true
maxretry = 3
bantime  = 86400

[postfix-sasl]
enabled  = true
port     = smtp,ssmtp,submission,imap2,imap3,imaps,pop3,pop3s
action   = iptables-multiport[name=postfix-sasl, port="smtp,ssmtp,465,submission,imap,imaps,pop3,pop3s"]
filter   = postfix-sasl
logpath  = /var/log/mail.log
maxretry = 2
findtime = 10800
bantime  = -1

[postfix]
enabled = true

[dovecot]
enabled = true

[apache-wp-login]
enabled  = true
port     = http,https
filter   = apache-wp-login
logpath  = /var/log/virtualmin/*access_log
maxretry = 1
findtime = 36000
bantime  = 36000

[apache-script-hackbots]
enabled  = true
port     = http,https
filter   = apache-script-hackbots
logpath  = /var/log/virtualmin/*access_log
maxretry = 1
findtime = 36000
bantime  = 36000

[apache-url-enumeration]
enabled  = true
port     = http,https
filter   = apache-url-enumeration
logpath  = /var/log/virtualmin/*access_log
maxretry = 5
findtime = 60
bantime  = 86400

[apache-probe-paths]
enabled  = true
port     = http,https
filter   = apache-probe-paths
logpath  = /var/log/virtualmin/*access_log
maxretry = 1
findtime = 36000
bantime  = 36000

[nginx-proxy-abuse]
enabled  = true
port     = http,https
filter   = nginx-proxy-abuse
logpath  = /var/log/nginx/*access.log
           /var/log/virtualmin/*access_log
maxretry = 1
findtime = 3600
bantime  = 604800

How fail2ban works

Fail2ban monitors log files and bans IPs that match filter patterns. When criteria match (e.g. 5 failed attempts within 60 seconds), it triggers an action — typically an iptables rule blocking that IP for a set duration.

Every IP address has an owner registered with a Regional Internet Registry (RIPE, ARIN, etc.) with a mandatory abuse contact email. If you're seeing persistent attacks, you can report them — the registry can take action against non-responsive owners.

Performance note

These filters use regex matching against potentially large log files. On low-resource servers, consider:

  • Using the -shorter variant instead of the full hackbots list
  • Increasing findtime to reduce scan frequency
  • Running fail2ban-regex to test performance before enabling

Tools

fail2ban-to-ipset.sh

Extract banned IPs from fail2ban (live jails, log files, or saved status dumps) into an ipset for permanent blocking.

# From a live jail
sudo tools/fail2ban-to-ipset.sh --jail postfix-sasl --set fail2ban-sasl

# From a saved status dump (e.g. historical sample)
sudo tools/fail2ban-to-ipset.sh --file samples/postfix-sasl-attackers-2025.txt --set smtp-attackers

# From fail2ban.log
sudo tools/fail2ban-to-ipset.sh --log /var/log/fail2ban.log --set historical

# Dry run
sudo tools/fail2ban-to-ipset.sh --jail sshd --set test --dry-run

Then enable blocking:

sudo iptables -I INPUT 1 -m set --match-set smtp-attackers src -j DROP

IP Blocklists (proactive blocking)

fail2ban is reactive — it bans IPs after they trigger a rule. For proactive blocking of known-bad IPs (hijacked netblocks, botnets, compromised hosts), use ipset with live-updated feeds like Spamhaus DROP or FireHOL.

See docs/ipset-blocklists.md for a complete setup guide with:

  • Recommended feeds (Spamhaus, FireHOL, AbuseIPDB, Emerging Threats)
  • Update script with atomic swap (no blocking gaps)
  • Integration with fail2ban so bans persist across restarts

AbuseIPDB Integration

Bidirectional integration with AbuseIPDB — report banned IPs to the community and pull their blocklist for proactive blocking.

1. Report banned IPs (outbound)

Add the abuseipdb-report action to any jail to auto-report when fail2ban bans an IP:

sudo cp action.d/abuseipdb-report.conf /etc/fail2ban/action.d/
sudo cp action.d/fail2ban-abuseipdb-report.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/fail2ban-abuseipdb-report.sh
sudo mkdir -p /var/cache/fail2ban-abuseipdb

Then in jail.local:

[apache-probe-paths]
# ... existing config ...
action = iptables-multiport[name=probe, port="http,https"]
         abuseipdb-report[category=21, api_key=YOUR_API_KEY]

Common category IDs: 15=Hacking, 18=Brute-Force, 19=Bad Web Bot, 21=Web App Attack, 22=SSH. Combine with comma: category="18,21".

Features:

  • 15-min cooldown per IP (AbuseIPDB policy)
  • Skips private/reserved IPs
  • Includes up to 5 log lines as evidence (truncated to 900 chars)
  • Logs to /var/log/fail2ban-abuseipdb.log

2. Pull blocklist (inbound)

Download AbuseIPDB's high-confidence bad IPs into an ipset for proactive blocking:

sudo cp tools/update-abuseipdb-blocklist.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/update-abuseipdb-blocklist.sh

# Run it
sudo ABUSEIPDB_API_KEY=xxx /usr/local/bin/update-abuseipdb-blocklist.sh

# Block the set
sudo iptables -I INPUT 1 -m set --match-set abuseipdb src -j DROP

Cron (every 6h, well under free tier 1000/day limit):

0 */6 * * * root ABUSEIPDB_API_KEY=xxx /usr/local/bin/update-abuseipdb-blocklist.sh

Features:

  • Atomic swap — no blocking gap during refresh
  • Configurable confidence threshold (--confidence 90, range 75-100)
  • Up to 10,000 IPs on free tier

Automatic Abuse Reports

The abuse-report action automatically looks up the abuse contact for a banned IP (via RDAP/whois across all registries — RIPE, ARIN, APNIC, LACNIC, AFRINIC) and sends a formatted complaint email with log evidence.

Install

sudo cp action.d/abuse-report.conf /etc/fail2ban/action.d/
sudo cp action.d/fail2ban-abuse-report.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/fail2ban-abuse-report.sh
sudo mkdir -p /var/cache/fail2ban-abuse

Requirements: curl, whois, mail (mailutils) or sendmail, jq (recommended) or python3

Usage

Add as a second action to any jail in jail.local:

[apache-script-hackbots]
enabled  = true
port     = http,https
filter   = apache-script-hackbots
logpath  = /var/log/virtualmin/*access_log
maxretry = 1
findtime = 36000
bantime  = 36000
action   = iptables-multiport[name=apache-script-hackbots, port="http,https"]
           abuse-report[sender=abuse@yourdomain.com, hostname=your-server.com]

Features

  • Multi-RIR: ARIN RDAP with automatic referrals to RIPE/APNIC/LACNIC/AFRINIC, whois fallback
  • Cooldown: same IP reported max once per 24h (configurable)
  • Skips private IPs: 10.x, 172.16-31.x, 192.168.x, 127.x
  • Evidence: includes up to 50 matching log lines
  • Logging: all reports logged to /var/log/fail2ban-abuse-reports.log

Check reports

# See what was sent
tail -20 /var/log/fail2ban-abuse-reports.log

# Test abuse lookup for an IP
/usr/local/bin/fail2ban-abuse-report.sh 47.76.209.138 test 5 you@example.com your-server ""

Sample attack data

The samples/ folder contains real-world attack logs and probe URLs used to build these filters. Useful for testing filters before deployment:

sudo fail2ban-regex samples/url-enumeration-attack.log filter.d/apache-url-enumeration.conf
sudo fail2ban-regex samples/probe-attack-urls.txt filter.d/apache-probe-paths.conf

See samples/README.md for details.

Contributing

These rules are based on real attacks. PRs welcome — especially for:

  • New attack patterns you've observed
  • Integration with IP reputation lists (AbuseIPDB, Spamhaus DROP, etc.)

About

A collection of custom rules for fail2ban

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages