DEV Community

Cover image for See who's attacking your server in Grafana — fail2ban metrics with Prometheus
Airat Iunusov
Airat Iunusov

Posted on Edited on

See who's attacking your server in Grafana — fail2ban metrics with Prometheus

Most people set up fail2ban and forget about it. It runs and bans IPs, but you only know what it does if you SSH in and read the logs.

This post adds fail2ban metrics to a Prometheus stack and shows them in Grafana, on the same board as CPU, memory and disk.

fail2ban dashboard in Grafana

What you will see

  • Current bans and failed attempts per jail
  • Total bans over time
  • Which jail fires most: SSH, Nginx or recidive
  • Whether the exporter can still reach fail2ban at all

The exporter gives counts and jail names. It does not put banned IP addresses into metrics, so you see how much is happening, not who.

Step 1: check the socket

The exporter reads the fail2ban control socket, not the log files.

test -S /var/run/fail2ban/fail2ban.sock && ls -l /var/run/fail2ban/fail2ban.sock
Enter fullscreen mode Exit fullscreen mode

Stop here if that path is missing or is a directory. Expected mode is owner-only, for example srw------- root root.

Step 2: add the exporter

Add this block to the services: section of your docker-compose.yml:

fail2ban-exporter:
  image: registry.gitlab.com/hctrdev/fail2ban-prometheus-exporter:0.10.3
  container_name: fail2ban-exporter
  restart: unless-stopped
  ports:
    - "127.0.0.1:9191:9191"
  volumes:
    - /var/run/fail2ban:/var/run/fail2ban:ro
  read_only: true
  security_opt:
    - no-new-privileges:true
  networks:
    - monitoring-net
  deploy:
    resources:
      limits:
        memory: 32M
Enter fullscreen mode Exit fullscreen mode

Two things I learned the hard way.

Mount the directory, not the socket file. If the file is missing when the container is created, Docker creates a directory at that path and fail2ban can no longer bind its socket.

This image runs as uid 0, so the socket stays owner-only and I limit the container instead: read-only mount, read_only, no-new-privileges, port on localhost.

Step 3: add a scrape job to Prometheus

In prometheus.yml:

- job_name: 'fail2ban'
  static_configs:
    - targets: ['fail2ban-exporter:9191']
      labels:
        instance: 'your-server'
  scrape_interval: 30s
  scrape_timeout: 10s
Enter fullscreen mode Exit fullscreen mode

Prometheus and the exporter must be on the same Compose network. Reload Prometheus, then check:

docker compose up -d fail2ban-exporter
curl -s http://127.0.0.1:9191/metrics | grep '^f2b_up'
Enter fullscreen mode Exit fullscreen mode

f2b_up must be 1. The Prometheus target can show UP while f2b_up is 0: the process is serving metrics but cannot use the socket.

Step 4: add the alerts

- alert: Fail2banHighAttackRate
  expr: increase(f2b_jail_banned_total[5m]) > 10
  labels:
    severity: warning
  annotations:
    summary: "High fail2ban ban rate on {{ $labels.instance }}"

- alert: Fail2banSocketDown
  expr: f2b_up == 0
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "fail2ban exporter cannot read the socket on {{ $labels.instance }}"
Enter fullscreen mode Exit fullscreen mode

The first one fires when more than 10 IPs are banned in 5 minutes. The second one covers the case above: the exporter is up but blind.

Step 5: import the dashboard

Download fail2ban.json from grafana-dashboards/json/ in the repo and import it in Grafana via Dashboards, Import. Select the fail2ban job in the $job dropdown.

Why bother

The first time I saw a ban spike in Grafana, 40 IPs banned in under 2 minutes at 3am, I realized I had had no view of this before. It was happening, I just could not see it.

One gotcha: by default systemd removes /var/run/fail2ban when fail2ban stops and creates a new directory on start. A container that mounted the old one keeps a deleted directory, and f2b_up stays 0. A systemd drop-in with RuntimeDirectoryPreserve=yes fixes it. I checked it on Ubuntu 24.04 (arm64) with fail2ban 1.0.2.

What I did not check: other distributions and other fail2ban versions.

Do you export fail2ban metrics, or do you still read the logs?

Repo: github.com/Airat71/monitoring-stack (guide: docs/FAIL2BAN_ENHANCED.md)

Disclosure: I used an AI assistant to run the repetitive checks and to help draft this text. The findings, the decisions and the fixes are mine.

Top comments (0)