fail2ban integration
Turn every IP that fail2ban bans on your server into a report to the ReportedIP community blacklist. A small custom action calls the report API on each ban, so your firewall and the community protect each other, the same data also feeds the blacklist feed and the DNS/RBL zone.
report permission. Create one for free in your
dashboard; see Authentication for limits. Report only IPs your own server actually caught.How fail2ban works (in 30 seconds)
fail2ban watches log files (or the systemd journal). A filter matches attack
patterns, a jail counts matches per IP within findtime, and once an IP
hits maxretry the jail runs its actions (ban + whatever else you add).
We add a second action that reports the banned IP to ReportedIP.
1. Create the report action
Save this as /etc/fail2ban/action.d/reportedip.conf. It POSTs the banned IP, the
threat categories and a short comment to the report endpoint on every ban.
# /etc/fail2ban/action.d/reportedip.conf
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -sS -m 10 -X POST "<report_url>" \
-H "X-Key: <api_key>" \
--data-urlencode "ip=<ip>" \
--data-urlencode "categories=<categories>" \
--data-urlencode "comment=fail2ban <name>: <failures> failed attempts" \
-o /dev/null || :
actionunban =
[Init]
report_url = https://reportedip.com/wp-json/reportedip/v2/report
api_key = <your-api-key>
categories = 18,22
Tighten the permissions so the key is not world-readable:
chmod 600 /etc/fail2ban/action.d/reportedip.conf
The tags <ip>, <name> (jail name) and <failures>
are filled in by fail2ban; <api_key>, <categories> and
<report_url> come from the [Init] block.
2. Enable it on a jail
Add the action to a jail in /etc/fail2ban/jail.local in addition to the
normal ban action (%(action_)s), not instead of it:
# /etc/fail2ban/jail.local
[sshd]
enabled = true
action = %(action_)s
reportedip
You can override the categories per jail by passing them inline, e.g.
reportedip[categories="16,21"] on a web-application jail.
3. Apply and verify
fail2ban-client reload
does not reliably attach it. Use systemctl restart fail2ban, then confirm both
actions are loaded.systemctl restart fail2ban
fail2ban-client get sshd actions
# -> The jail sshd has the following actions:
# nftables, reportedip
Reading the fail2ban log
fail2ban logs to /var/log/fail2ban.log (or the journal: journalctl -u fail2ban).
The lines you care about:
2026-05-30 21:56:18 fail2ban.filter [38760]: INFO [sshd] Found 203.0.113.45 - 2026-05-30 21:56:18
2026-05-30 21:56:26 fail2ban.actions [38760]: NOTICE [sshd] Ban 203.0.113.45
2026-05-30 22:06:26 fail2ban.actions [38760]: NOTICE [sshd] Unban 203.0.113.45
| Log line | Meaning |
|---|---|
Found <ip> | The filter matched one attack attempt. Not yet banned. |
Ban <ip> | Threshold reached, the jail ran its actions, including the report to ReportedIP. |
Unban <ip> | bantime elapsed; the firewall rule was removed (no report). |
Restore Ban <ip> | fail2ban restarted and re-applied a still-active ban. |
The report action itself is silent on success (-o /dev/null). To watch reports go out,
tail the log while testing, or run the action's curl by hand for one IP, a success
returns the new report:
{"data":{"ipAddress":"203.0.113.45","abuseConfidencePercentage":39,"reportId":"4316026","aggregated":true}}
Live counters: check your key with
curl -H "X-Key: <key>" https://reportedip.com/wp-json/reportedip/v2/verify-key and read
limits.dailyReportUsage, or open the dashboard.
Choosing threat categories per jail
Pass the category IDs that match what the jail detects. Common mappings (full list in Threat Categories):
| Jail | categories | Meaning |
|---|---|---|
sshd | 18,22 | Brute-Force, SSH |
postfix-sasl, dovecot | 11,18 | Email Spam, Brute-Force |
proftpd, vsftpd | 5 | FTP Brute-Force |
nginx-http-auth, apache-auth | 18,21 | Brute-Force, Web App Attack |
nginx-badbots, apache-badbots | 14,19 | Port Scan, Bad Web Bot |
apache-sqli, web exploits | 16,21 | SQL Injection, Web App Attack |
WordPress / wp-login | 18,21 | Brute-Force, Web App Attack |
recidive (repeat offenders) | 15,18 | Hacking, Brute-Force |
The bot jails are the ones that produce false reports: a jail that bans by User-Agent catches a real crawler sooner or later. Before reporting such an address, run it through Bot Verification, which applies the method the crawler operator itself publishes.
Reporting more than one jail
Attach the action to each jail and override the categories inline so each report is accurate:
# /etc/fail2ban/jail.local
[sshd]
enabled = true
action = %(action_)s
reportedip[categories="18,22"]
[postfix-sasl]
enabled = true
action = %(action_)s
reportedip[categories="11,18"]
[recidive]
enabled = true
action = %(action_)s
reportedip[categories="15,18"]
Good practice & safety
- Only report your own bans. Never report IPs you have not observed attacking your own systems, false reports hurt the dataset (and may get your key suspended).
- Whitelist your own ranges. Keep
ignoreipset for your office/monitoring IPs so you never report yourself. - Keep the key private (
chmod 600). It is not exposed to attackers, but anyone with the file can submit reports as you. Rotate it from the dashboard if leaked. - A failed report never blocks a ban. The
|| :at the end ofactionbanmeans a network hiccup at ReportedIP won't stop fail2ban from banning locally. - Sensible thresholds. Very low
maxretryon noisy jails can over-report transient clients; the defaults (3–5) are fine.
Troubleshooting
| Symptom | Fix |
|---|---|
get <jail> actions shows only the ban action | You reloaded instead of restarting. Run systemctl restart fail2ban. |
unknown smtpd restriction / jail won't start | Config typo in jail.local; check indentation of the continuation line under action. |
| Reports rejected (HTTP 401/403) | Key missing the report permission, or wrong header, it must be X-Key. |
| HTTP 429 / quota | Daily report limit reached for your plan; see Authentication. |
| Nothing in the log on ban | The report action is silent on success. Confirm with fail2ban-client get <jail> actions and a manual curl. |
Last updated: · Maintained by the ReportedIP team