All posts
July 3, 20265 min read

fail2ban, but every ban helps everyone

fail2ban only knows the IPs that already attacked you. Wire it to a shared, free abuse database and block attackers before they ever reach you.

fail2ban is great at one thing: watching your logs and banning an IP after it fails to log in too many times. But it has a blind spot. It only knows about IPs that have already attacked you. The IP that brute-forced your neighbour's server last night is a total stranger to your machine until it knocks on your door too. The fix is a shared abuse database. If everyone's fail2ban reports its bans to one free, open pool, then everyone can check that pool and block a known attacker on its very first packet. Here is how to wire ffraud into fail2ban in both directions, for free, no key required for the lookups.

#1. Block known-bad IPs before they touch you

Before you trust a connection, ask whether the IP is already known for abuse. One request, no key:

curl https://api.ffraud.com/public/ip/45.146.164.110

The response tells you the fraud_score (0-100), whether it's a vpn, proxy, tor exit or hosting IP, and threat_tags like ssh_brute_force or c2_server so you know *why* it's flagged. Pick a threshold that fits your risk appetite and reject above it.

#2. Report every ban (the part that helps everyone)

This is the half most blocklists skip. When your fail2ban bans an IP, tell the shared pool. It costs you nothing and it's what turns a pile of private bans into a database that catches attackers early for the whole community. ffraud speaks the same report format as the tool you may already use, so it's a drop-in action.

Create /etc/fail2ban/action.d/ffraud.conf:

action.d/ffraud.conf
[Definition]
actionban = curl -s -X POST "https://api.ffraud.com/compat/abuseipdb/v2/report" \
  -H "Key: <YOUR_FREE_FFRAUD_KEY>" \
  --data-urlencode "ip=<ip>" \
  --data-urlencode "categories=18,22" \
  --data-urlencode "comment=fail2ban: <name> jail"

Then reference it from any jail in jail.local, alongside your normal ban action:

jail.local
[sshd]
enabled = true
action  = %(action_)s
          ffraud

Grab a free key at ffraud.com/register for reporting. Lookups stay keyless. Reports are weighted by your account's track record, so one bad actor can't poison the pool, and a two-signal rule means a single report never brands an IP on its own.

#3. Or pull the whole blocklist and never call an API

If you'd rather not add a network hop, download the entire confirmed-abusive set once a day and load it into your firewall. It's plain text, one IP per line, MIT licensed, rebuilt every 30 minutes:

cron (daily) + ipset
# The list is a CSV: skip the # comment header and the column row,
# then take the first field (the IP).
curl -s https://raw.githubusercontent.com/FFraud-com/ip-fraud-database/main/threat-ips/confirmed-abusive.csv \
  | grep -v '^#' | tail -n +2 | cut -d, -f1 \
  | while read ip; do ipset add ffraud "$ip" -exist; done

Point a single iptables rule at that ipset and you're dropping 750,000+ confirmed-abusive IPs at the kernel with zero per-request latency. Each row also carries the ffraud_score, the number of independent confirmations, the threat category, and the infrastructure type, so you can filter to just the ones you care about.

#Why the shared model wins

A private ban list protects you from repeat offenders. A shared one protects you from first-time offenders, because someone else already met them. Every ban you contribute makes the next lookup smarter, and the whole thing is free and open precisely so that the network effect belongs to everyone, not to whoever charges the most for it. The data behind every score comes from our own honeypot sensors and community reports like yours, never from reselling someone else's feed. That's why we can give it away.