Who’s Knocking? Turning fail2ban Emails into a Map of My Attackers

AttackMap is a small C# program that reads fail2ban’s ban emails and turns them into a web page showing which countries are attacking your server.

The pile of emails nobody reads

If you run fail2ban with email alerts switched on, you know the drill. Every time someone hammers your server with bad logins, fail2ban blocks their IP address and sends you a message about it.

One of those emails tells you almost nothing. Hundreds of them, added up, answer a much more interesting question: who is actually attacking my server, and from where?

The answer was already sitting in my inbox. It just needed counting. So I wrote a program to read the emails, look up each attacker’s country, and publish the results as a page on my WordPress site.

What AttackMap gives you

AttackMap builds one self-contained web page that updates itself every morning. It shows:

  • Four headline numbers: total bans, unique attacking IPs, how many countries they came from, and bans in the last 7 days.
  • A world map with each country shaded by how many bans it has racked up. Hover over a country to see its count.
  • A top-countries bar chart, with the full list of countries in a table underneath.
  • Attacks over time, by day at first and switching to weeks once there are about four months of history.
  • A table of the worst offenders: the IP addresses banned most often, their country, when they were last banned, and which fail2ban jails caught them (sshd, nginx, postfix and so on).

It works in light or dark mode, fits on a phone, and sits inside a normal WordPress page.

How it works

Each run goes through five steps:

  1. Read the mailbox, read-only. It logs in over IMAP, searches for emails from fail2ban’s sender address, and fetches only the ones it hasn’t seen before. It never moves, marks or deletes anything.
  2. Pull out the attacker. fail2ban’s standard subject line looks like [Fail2Ban] sshd: banned 203.0.113.7 from myserver, so the IP address and jail come straight from the subject without downloading the email. If a subject is unusual, it checks the body for fail2ban’s “The IP … has just been banned” line. It never just grabs the first IP it finds, because the whois versions of the email are full of other addresses.
  3. Look up the country offline. A free IP-to-country database file sits on the server, so there are no API calls, rate limits or fees.
  4. Add it to the history. Every ban goes into a small JSON file. That’s what lets the page show all-time numbers even after you clear out old emails, and it guarantees no email is ever counted twice.
  5. Write the page. It builds one HTML file and swaps it into place in a single step, so a visitor never catches it half-written.

The whole thing is about 900 lines of C# on .NET 10. It uses MailKit for email and MaxMind’s GeoIP2 reader for lookups, and the page draws its charts with Chart.js and jsvectormap.

Getting it running

You need a Linux box with fail2ban sending ban emails, an IMAP mailbox those emails land in, and a web server. These steps are for Ubuntu 24.04; the README in the repo has the full detail.

1. Install .NET 10 and build. .NET 10 is the current long-term support release.

sudo apt install dotnet-sdk-10.0
git clone <repo-url> attackmap
cd attackmap
dotnet publish -c Release -o /opt/attackmap

2. Get the free GeoIP database. The DB-IP IP to Country Lite database is free, needs no account and comes out monthly. The included update-geoip.sh script downloads the latest copy into /opt/attackmap.

3. Fill in the settings.

cd /opt/attackmap
cp appsettings.example.json appsettings.json
chmod 600 appsettings.json
nano appsettings.json

The ones that matter most:

SettingWhat to put
Imap.Host, Port, SecurityYour mail provider’s IMAP details, usually port 993 with SslOnConnect
Imap.Username, PasswordYour mailbox login (some providers want an app password)
Imap.SenderFilterThe address fail2ban sends from, as set in jail.local
OutputFileWhere the page goes, e.g. /var/www/html/attacks/index.html

4. Run it once.

/opt/attackmap/attackmap

It reads every fail2ban email in the mailbox the first time, then prints something like Checked mail: 14 new email(s), 11 ban(s) added, 2 skipped. The skipped ones are fail2ban’s “jail started” and “jail stopped” notices, which have no attacker in them.

5. Put it on your WordPress site. Add a Custom HTML block to any page:

<iframe src="/attackmap/" title="Server attack map" loading="lazy"
        style="width:100%;height:2400px;border:0"
        onload="this.style.height=this.contentWindow.document.documentElement.scrollHeight+'px'"></iframe>

Because the page lives on the same site as WordPress, the frame sizes itself to fit.

Running it every morning

Two cron lines keep everything up to date: the page at 6:30 every morning, and the GeoIP database at 6:00 on the 3rd of each month, giving each new DB-IP release a couple of days to appear.

30 6 * * * /opt/attackmap/attackmap >> /var/log/attackmap.log 2>&1
0 6 3 * * /opt/attackmap/update-geoip.sh >> /var/log/attackmap.log 2>&1

Add them with crontab -e as whichever account runs AttackMap. Cron uses the server’s own time zone, so check timedatectl first; if it says UTC, 6:30 isn’t 6:30 your time.

Gotchas I hit along the way

  • Which account runs it. Running it as your own user is the simplest option. A dedicated low-privilege account is safer, because the program reads text written by attackers. If you go that way, give the program to your own account so the running account can’t modify it, and use sudo crontab -u <account> -e. Don’t put sudo -u in your own crontab: cron can’t type your password, so it fails silently.
  • The history file needs somewhere writable. If the program folder isn’t writable by the account that runs it, point StateFile somewhere that is, like /var/lib/attackmap. Keep it out of the web folder, or anyone can download your whole ban history.
  • index.html changes owner on every run. The page is written to a temporary file and swapped in, so the new file belongs to whoever ran the program. The web server only needs to read it, so this is harmless. If you want the group to stay www-data, run chmod 2755 on the folder so new files inherit its group.
  • A shared lock on the GeoIP database. Early versions memory-mapped the database, and the library guards that with a lock tied to your login session. Running it as one account and then another from the same session failed with a cryptic “Mutex” error. AttackMap now loads the database straight into memory instead. A country database is only a few megabytes, so it costs nothing.

Try it yourself

If fail2ban is already emailing you, you’re most of the way there. Grab the code, point it at your mailbox, and by tomorrow morning you’ll have your own map of who’s knocking.`

Get AttackMap on GitHub: paulfarr0w/attackmap

If your fail2ban emails use a custom format and show up as “skipped”, the matching patterns sit at the top of Fail2BanParser.cs, and issues and pull requests are welcome.

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.