Guides
fail2ban monitoring, dashboards and web UIs
fail2ban has no metrics endpoint and no official GUI, but its socket answers every status question. An exporter turns that into Prometheus metrics for Grafana, a small script feeds node_exporter or Zabbix, and third-party web UIs add buttons. This guide covers each option and what to check before you expose any of them.
To monitor fail2ban, run fail2ban-prometheus-exporter (by Hector Smith, on GitLab as hctrdev/fail2ban-prometheus-exporter). It reads /var/run/fail2ban/fail2ban.sock and serves metrics such as f2b_jail_banned_current{jail="sshd"} on port 9191 for Prometheus and the project's Grafana dashboard. fail2ban has no official GUI; Webmin's module or a third-party web UI such as fail2ban-ui adds one.
What there is to monitor#
Every graph, dashboard and GUI shows the same data fail2ban-client gives you:
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 6
| `- File list: /var/log/auth.log
`- Actions
|- Currently banned: 1
|- Total banned: 1
`- Banned IP list: 203.0.113.42Useful signals: is fail2ban up, how many IPs are banned per jail, how fast bans happen, and whether a jail that should be busy has gone quiet (a broken filter or log path). The "total" counters reset when fail2ban restarts. Before installing a GUI, check whether the status and banned IPs commands, or the command builder, already cover what you need.
Prometheus exporter: fail2ban-prometheus-exporter#
A Go exporter that talks to the fail2ban socket. The latest release at the time of writing is v0.10.3 (April 2025). Binaries are on the project's GitLab releases page, and it's packaged in the AUR and nixpkgs.
Run it as a binary#
sudo fail2ban_exporter --collector.f2b.socket=/var/run/fail2ban/fail2ban.sock --web.listen-address=":9191"The socket belongs to root, so the exporter has to run as root (or as a user with access to the socket). The project ships an example systemd unit (_examples/systemd/fail2ban_exporter.service). Bind it to localhost or a private interface if Prometheus runs on the same network: --web.listen-address="127.0.0.1:9191". Basic-auth flags are available if it must listen publicly.
Run it in Docker#
services:
fail2ban-exporter:
image: registry.gitlab.com/hctrdev/fail2ban-prometheus-exporter:latest
volumes:
- /var/run/fail2ban/:/var/run/fail2ban:ro
ports:
- "127.0.0.1:9191:9191"
restart: unless-stoppedMount the directory, not the socket file. fail2ban deletes and re-creates the socket on every restart, and a file bind mount would keep pointing at the deleted one. If fail2ban itself runs in a container, share its /var/run/fail2ban volume with the exporter instead (see the Docker guide). Every flag also has an environment variable (F2B_COLLECTOR_SOCKET, F2B_WEB_LISTEN_ADDRESS).
Metrics#
All names start with f2b_:
| Metric | Meaning |
|---|---|
f2b_up |
1 if the exporter is running |
f2b_errors{type="socket_conn"}, {type="socket_req"} |
Errors talking to the fail2ban socket |
f2b_jail_count |
Number of jails |
f2b_jail_banned_current{jail} |
IPs banned right now |
f2b_jail_banned_total{jail} |
Bans since fail2ban started (includes expired) |
f2b_jail_failed_current{jail} |
Current failures being counted |
f2b_jail_failed_total{jail} |
Failures since fail2ban started |
| ban time, find time, max retry per jail | The jail's configuration, in seconds / count |
f2b_version |
Exporter and fail2ban versions as labels |
Prometheus scrape config and alerts#
scrape_configs:
- job_name: fail2ban
static_configs:
- targets: ["server1.example.com:9191", "server2.example.com:9191"]groups:
- name: fail2ban
rules:
- alert: Fail2banSocketErrors
expr: increase(f2b_errors{type="socket_conn"}[10m]) > 0
for: 10m
annotations:
summary: "fail2ban not reachable on {{ $labels.instance }}"
- alert: Fail2banBanSurge
expr: increase(f2b_jail_banned_total[1h]) > 200
annotations:
summary: "{{ $labels.jail }} banned more than 200 IPs in an hour on {{ $labels.instance }}"
- alert: Fail2banExporterDown
expr: up{job="fail2ban"} == 0
for: 5mincrease() copes with the counter reset after a fail2ban restart. The socket-error alert catches the most important failure: fail2ban stopped, and nothing is protecting the server. Tune the surge threshold to your normal rate.
Grafana dashboard for fail2ban#
The exporter repository includes a sample dashboard at _examples/grafana/dashboard.json (Grafana 9.1.8 or newer). In Grafana: Dashboards → New → Import, paste the JSON, pick your Prometheus data source. It has an instance variable for multiple servers.
Dashboards on grafana.com titled "fail2ban" are built for different exporters, and their metric names (such as fail2ban_banned_current) won't match this exporter's f2b_ metrics. Check a panel's query before importing one.
Other exporters exist, for example fail2ban-exporter by dyp2000 (Go, fail2ban_* metrics) and fail2ban-geo-exporter, which exports one series per banned IP with GeoIP labels for map panels. Per-IP series create high cardinality on busy servers.
node_exporter textfile collector#
If node_exporter already runs on the host, skip the extra daemon. A script writes a .prom file every minute and node_exporter's textfile collector serves it:
#!/bin/sh
# Write fail2ban metrics for node_exporter's textfile collector.
set -eu
DIR=${1:-/var/lib/prometheus/node-exporter}
OUT="$DIR/fail2ban.prom"
TMP="$OUT.$$"
metric() { # name type help label-in-status
echo "# HELP $1 $3"
echo "# TYPE $1 $2"
for j in $JAILS; do
fail2ban-client status "$j" |
awk -F'\t' -v j="$j" -v m="$1" -v k="$4:" '$0 ~ k {print m "{jail=\"" j "\"} " $2}'
done
}
{
echo '# HELP fail2ban_up 1 if fail2ban-client could reach the server.'
echo '# TYPE fail2ban_up gauge'
if fail2ban-client ping >/dev/null 2>&1; then
echo 'fail2ban_up 1'
JAILS=$(fail2ban-client status | awk -F'\t' '/Jail list:/ {print $2}' | tr ',' ' ')
metric fail2ban_banned_current gauge 'IPs currently banned per jail.' 'Currently banned'
metric fail2ban_failed_current gauge 'Current failures per jail.' 'Currently failed'
metric fail2ban_banned_total counter 'Bans since the jail started.' 'Total banned'
else
echo 'fail2ban_up 0'
fi
} > "$TMP"
mv "$TMP" "$OUT"It relies on the tab-separated fail2ban-client status output, writes to a temporary file and renames it so node_exporter never reads a half-written file. Run it from root's crontab:
sudo chmod 755 /usr/local/bin/fail2ban-textfile.sh
echo '* * * * * root /usr/local/bin/fail2ban-textfile.sh /var/lib/prometheus/node-exporter' | sudo tee /etc/cron.d/fail2ban-textfilePoint node_exporter at the directory with --collector.textfile.directory=/var/lib/prometheus/node-exporter. Debian's prometheus-node-exporter package is set up to read that directory. Alert on fail2ban_up == 0.
Zabbix#
Zabbix's integrations page lists a community fail2ban template with jail discovery (low-level discovery), service status and ban graphs, for Zabbix 5.0 and newer. If you'd rather build it yourself, three agent items are enough. The agent runs as the zabbix user, which can't open the fail2ban socket, so give it a narrow sudo rule:
#!/bin/sh
# f2b-zabbix.sh discover | banned <jail> | failed <jail>
case "$1" in
discover)
fail2ban-client status | awk -F'\t' '/Jail list:/ {print $2}' | tr ',' '\n' |
awk 'NF {printf "%s{\"{#JAIL}\":\"%s\"}", (n++ ? "," : "["), $1} END {print (n ? "]" : "[]")}' ;;
banned) fail2ban-client status "$2" | awk -F'\t' '/Currently banned:/ {print $2}' ;;
failed) fail2ban-client status "$2" | awk -F'\t' '/Currently failed:/ {print $2}' ;;
esaczabbix ALL=(root) NOPASSWD: /usr/local/bin/f2b-zabbix.shUserParameter=fail2ban.discovery,sudo /usr/local/bin/f2b-zabbix.sh discover
UserParameter=fail2ban.banned[*],sudo /usr/local/bin/f2b-zabbix.sh banned "$1"
UserParameter=fail2ban.failed[*],sudo /usr/local/bin/f2b-zabbix.sh failed "$1"Make the script root-owned and not writable by others (chown root:root, chmod 755), since zabbix may run it as root. In Zabbix, create a discovery rule on fail2ban.discovery with item prototypes fail2ban.banned[{#JAIL}] and fail2ban.failed[{#JAIL}]. Add a proc.num[fail2ban-server] item with a trigger on = 0 for outages. For the classic agent the directory is /etc/zabbix/zabbix_agentd.d/.
Alerts from the fail2ban log#
For a single server, the fail2ban log is often enough. Every ban is one line:
2026-10-25 18:36:11,027 fail2ban.actions [1997]: NOTICE [sshd] Ban 203.0.113.42- Loki / Promtail or Grafana Alloy: ship
/var/log/fail2ban.logand count bans with a LogQL query such assum by (jail) (count_over_time({job="fail2ban"} |= "] Ban " | regexp "\\[(?P<jail>[^\\]]+)\\] Ban" [5m])). - Email or chat per ban: fail2ban can send mail on each ban (email alerts), or push to 120+ services with the
appriseaction. For busy jails, a daily summary is more useful than one message per ban. - Quick look without tooling:
grep -c 'Ban ' /var/log/fail2ban.logor the log analyzer. Field-by-field explanations are on the fail2ban log page.
Does fail2ban have a GUI?#
No. The upstream project ships only the fail2ban-server daemon and the fail2ban-client command line. fail2ban runs as root because it changes firewall rules, and it's controlled through a Unix socket (/var/run/fail2ban/fail2ban.sock). Anything that can talk to that socket can ban, unban, reconfigure jails and even define action commands, which fail2ban then executes as root. A web UI is a network-facing front end to that socket and inherits all of that power, so the project has stayed a small, auditable daemon and left interfaces to others.
fail2ban GUIs and web UIs compared#
| Tool | Type | Can change things | Notes |
|---|---|---|---|
| Webmin, "Fail2Ban Intrusion Detector" module | Server admin panel module | Yes: filters, actions, jails, unban, service | Included in Webmin; under Networking |
| fail2ban-ui (Swissmakers) | Standalone web app (Go), container image | Yes: bans, unbans, jails, filters | Manages several hosts (local socket, SSH, or agent); optional OIDC login |
| fail2ban-dashboard (webishdev) | Web dashboard (Go), container image | Mostly viewing | Reads the socket; basic auth or OAuth2; optional Prometheus metrics |
| fail2web (isavage) | Docker bundle of fail2ban + web UI | Yes | Packaged for docker-compose setups |
| fail2web + fail2rest (Sean-Der) | Web UI over a REST server | Yes | Older project; check its commit history before relying on it |
| Plesk, HestiaCP and other hosting panels | Built into the panel | Yes, within the panel | Plesk calls it "IP Address Banning (Fail2Ban)" |
| FreePBX Intrusion Detection | Built into FreePBX | Yes | Writes fail2ban's config itself. See the Asterisk guide |
| Grafana via an exporter | Dashboards and alerts | No (read-only) | Best for graphs over time and many servers |
Third-party projects change quickly. Look at each repository's recent commits, open issues and license before deploying, and pin a version tag instead of :latest in production.
Webmin includes a Fail2Ban module, listed as Fail2Ban Intrusion Detector under Networking. It can list and edit filters (regex and log targets), actions and jails, show each jail's status and active bans with an unblock button, change logging and socket settings, edit the config files directly, and start, stop or restart the service. If it doesn't appear after installing fail2ban, use Refresh Modules and check that your Webmin user is allowed to use the module. Webmin itself listens on port 10000 with root-level access to the whole server. Treat it like SSH: restrict it to your IP or VPN and enable two-factor authentication.
fail2ban-ui by Swissmakers is a newer, actively developed web interface written in Go. It can manage several fail2ban hosts from one place: on the same host through the socket, over SSH with a key, or through a small agent. It covers reviewing and searching bans, unbanning, managing jails and filters, and notifications, and it supports OIDC login (Keycloak, Authentik, Pocket-ID). It runs as a container (swissmakers/fail2ban-ui) under Docker or Podman.
fail2ban-dashboard by webishdev is a lightweight Go dashboard that reads the fail2ban socket and shows jails and bans. It supports basic auth and OAuth2 and can expose Prometheus metrics. The container image is ghcr.io/webishdev/fail2ban-dashboard. Because the socket is usually root-only, its Docker example runs the container as root with the socket directory mounted.
Security checklist for any fail2ban web UI#
- Never expose it to the internet. Bind it to
127.0.0.1and reach it over an SSH tunnel (ssh -L 8080:127.0.0.1:8080 server) or a VPN. If it must sit behind a reverse proxy, restrict the proxy by source IP as well. - Turn on authentication, even on localhost, and prefer SSO/OIDC or strong unique passwords. Some tools ship with authentication disabled by default.
- Use TLS if it isn't localhost-only, so session cookies and credentials aren't sent in clear text.
- Mount the socket read-only where possible and don't give the container
--privilegedor host networking unless the tool needs them. - Whitelist your admin IP in fail2ban's
ignoreip, so a misclick or a typo in a UI-edited jail can't ban you. - Keep it updated and watch the project for security advisories. A web UI adds attack surface that fail2ban alone doesn't have.
More hardening advice is on the security page.
DIY: a read-only status page#
If you only want to see bans in a browser, skip the web app and publish a static page generated by root's crontab. Nothing on the web side can talk to the socket:
#!/bin/sh
# Write a static, read-only fail2ban status page (fail2ban 1.1.0+ for status --all).
set -eu
OUT=/var/www/f2b-status/index.html
TMP="$OUT.tmp"
{
echo '<!doctype html><meta charset="utf-8"><title>fail2ban status</title><pre>'
date
fail2ban-client status --all | sed 's/&/\&/g; s/</\</g; s/>/\>/g'
echo '</pre>'
} > "$TMP"
mv "$TMP" "$OUT"sudo install -d /var/www/f2b-status
sudo chmod 755 /usr/local/bin/f2b-status-page.sh
echo '* * * * * root /usr/local/bin/f2b-status-page.sh' | sudo tee /etc/cron.d/f2b-status-pageServe /var/www/f2b-status from your web server behind basic auth and an IP allowlist. Banned addresses are other people's personal data in some jurisdictions, so the page shouldn't be public. On fail2ban 1.0.x, which lacks status --all, loop over the jails instead: for j in $(fail2ban-client status | awk -F'\t' '/Jail list:/{print $2}' | tr ',' ' '); do fail2ban-client status "$j"; done.
FAQ#
Does fail2ban have a GUI?#
No. fail2ban ships only the fail2ban-server daemon and the fail2ban-client command line. Graphical options come from third parties: Webmin's Fail2Ban Intrusion Detector module, web apps such as fail2ban-ui and fail2ban-dashboard, hosting panels like Plesk, or Grafana dashboards fed by a Prometheus exporter.
Is there a Prometheus exporter for fail2ban?#
Yes. The most used is fail2ban-prometheus-exporter by Hector Smith (hctrdev/fail2ban-prometheus-exporter on GitLab). It reads the fail2ban socket and serves f2b_-prefixed metrics on port 9191. In Docker, use registry.gitlab.com/hctrdev/fail2ban-prometheus-exporter:latest and mount the host directory /var/run/fail2ban/ read-only, not the socket file.
Is there a Grafana dashboard for fail2ban?#
The exporter project ships one as _examples/grafana/dashboard.json. Import it in Grafana under Dashboards → Import (Grafana 9.1.8+). Dashboards on grafana.com often target other exporters with different metric names, so check their queries match f2b_jail_banned_current before using them.
How do I monitor fail2ban with Zabbix?#
Use the community fail2ban template listed on Zabbix's integrations page, or add UserParameter items that call a small root-owned script through sudo. The script parses fail2ban-client status for jail discovery and per-jail banned and failed counts. Also monitor proc.num[fail2ban-server] to catch a stopped service.
Is there a fail2ban web UI for Docker, and is it safe to expose?#
fail2ban-ui (swissmakers/fail2ban-ui) and fail2ban-dashboard (ghcr.io/webishdev/fail2ban-dashboard) run as containers and talk to fail2ban through its socket mounted from the host; fail2web bundles both. Don't expose any of them publicly: socket access is root-equivalent. Bind to localhost, use an SSH tunnel or VPN, and enable authentication.
What is the best fail2ban dashboard?#
For viewing bans over time and across servers, a Prometheus exporter with Grafana is the most robust and read-only. For clicking to unban or edit jails, Webmin's module is the long-standing choice, and fail2ban-ui is a newer multi-host option. For a single server, fail2ban-client status --all may be all you need.
What should I alert on for fail2ban?#
First, fail2ban being down or unreachable (f2b_errors rising, fail2ban_up == 0, or no fail2ban-server process), because then nothing is protecting the server. Second, unusual ban surges per jail. Third, a normally busy jail with zero failures for a day, which often means a broken log path or filter.
Spotted something wrong or out of date? Tell us — every page is checked against the current fail2ban release.