Files
fail2ban-webui/docs/operations.md
T
schiwagoaandClaude Opus 5.5 2e3f79236c Initial version of fail2ban-webui
Web interface to view, ban and unban fail2ban IPs on several hosts via
SSH, generalised from the private f2b-web tool.

- Web UI (Flask/Waitress): hosts.yaml, local/OIDC/proxy authentication,
  English/German UI, origin map, optional Loki/Grafana/GeoIP, CLI helpers
- Agent f2b-webctl with install.sh/uninstall.sh (dedicated user + sudo,
  from= restriction, protected networks, manual jail)
- Hardened Docker image, examples, documentation, smoke test, testbed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:38:17 +01:00

6.0 KiB
Raw Blame History

Operations

All commands are run in the directory with docker-compose.yaml (e.g. /opt/fail2ban-webui).

Status and logs

docker compose ps                              # Up … (healthy)
docker compose logs --tail 100 fail2ban-webui
docker compose logs -f fail2ban-webui | grep -E 'AUDIT|LOGIN|ERROR|WARNING'
docker compose run --rm fail2ban-webui check   # every agent reachable?

Log lines of interest:

Line Meaning
fail2ban-webui 1.0.0 – hosts: … – auth: … – loki: … – geoip: … start summary
AUDIT {…} ban/unban with user, client IP, hosts and result
LOGIN_OK / LOGIN_FAILED / LOGIN_LOCKED / LOGOUT sign-in events
OIDC_FAILED / OIDC_DENIED SSO errors / account not allowed

The audit log is also kept in data/audit.log (one JSON object per line):

tail -n 20 data/audit.log | python3 -m json.tool --json-lines

On the hosts every agent action is in syslog:

journalctl -t f2b-webctl --since today

Update

cp -a .env docker-compose.yaml config /backups/fail2ban-webui_$(date +%F)/   # or your backup location
docker compose pull
docker compose up -d
docker compose ps
docker compose run --rm fail2ban-webui check

Read the changelog first – if it mentions a new agent version, update the agents as well (re-run install.sh with the same options, see agent.md). The 1 tag only moves within the 1.x series; breaking changes get a new major version.

Rebuild/pull regularly even without new features: the image is rebuilt with current Debian security updates.

Backup

What Why
.env settings, password hash, OIDC secret
config/hosts.yaml host list
data/ssh/ private key and pinned host keys – without them every agent needs the new key
data/audit.log audit trail
data/secret_key only keeps sessions valid; losing it signs everybody out

data/geoip/ is downloaded again automatically.

tar -cpzf /backups/fail2ban-webui_$(date +%F_%H-%M).tar.gz .env docker-compose.yaml config data
sha256sum /backups/fail2ban-webui_*.tar.gz | tail -n1

The archive contains the private SSH key – store it accordingly.

Moving to another host

  1. docker compose stop on the old host, create the archive as above.
  2. Copy it, unpack with tar -xpzf (keeps owner 10050 and permissions).
  3. On every agent add the new address to from= – easiest by re-running install.sh … --from <new address>.
  4. docker compose up -d, then check.

Rotating the SSH key

docker compose run --rm fail2ban-webui keygen --force
# on every host:
bash install.sh --key-file new.pub --from … (same options as before)

The installer replaces only entries with the same key; remove the old one with uninstall.sh first or delete its line from authorized_keys (it contains command="…f2b-webctl").

Protecting the login with fail2ban

The UI locks a client after 5 failed logins itself. To ban persistent attackers on the firewall, let fail2ban on the proxy host read the container log.

/etc/fail2ban/filter.d/fail2ban-webui.conf

[Definition]
# Docker json-file log: {"log":"… WARNING webui: LOGIN_FAILED user=x ip=203.0.113.7\n",…}
# user names never contain spaces (they are sanitised), so only the real " ip=" matches
failregex = LOGIN_FAILED user=\S* ip=<HOST>
            LOGIN_LOCKED ip=<HOST>
ignoreregex =

/etc/fail2ban/jail.d/fail2ban-webui.local

[fail2ban-webui]
enabled  = true
filter   = fail2ban-webui
backend  = polling
logpath  = /var/lib/docker/containers/*/*-json.log
maxretry = 5
findtime = 1h
bantime  = 1d
# the proxy runs in Docker? Then ban in the DOCKER-USER chain:
# banaction = iptables-allports[chain=DOCKER-USER]
fail2ban-client reload
fail2ban-regex /var/lib/docker/containers/*/*-json.log /etc/fail2ban/filter.d/fail2ban-webui.conf

This only works when the container sees the real client address (TRUSTED_PROXY_HOPS set correctly). User names are logged with spaces and control characters replaced, so a crafted user name cannot inject a second log line.

Troubleshooting

Symptom Cause / fix
Container exits: … is not writable for UID 10050 chown -R 10050:10050 data
Container exits: AUTH_MODE=local needs AUTH_PASSWORD_HASH run hash-password, paste the line with single quotes
Container exits: No hosts configured config/hosts.yaml missing or empty; on Windows/Docker Desktop: the drive cannot be bind-mounted
Login always fails after pasting the hash single quotes missing in .env – Compose ate the $ parts
Everybody locked out together / audit shows the proxy IP TRUSTED_PROXY_HOPS not set
Host key verification failed host not in known_hosts or its key changed → keyscan, compare fingerprints
Permission denied (publickey) key not installed for that user, wrong user: in hosts.yaml, wrong from= (the address the host sees!), or sshd does not read that authorized_keys file
sudo: a password is required sudo rule missing → re-run install.sh
The agent does not run as root forced command without sudo -n for a non-root user → re-run install.sh
Timeout talking to … firewall/port, or the host is slow – raise SSH_TIMEOUT
The jail manual-block does not exist on this host installed with --no-jail or with another --manual-jail; check MANUAL_JAIL in /etc/f2b-webctl.conf
Bans have no start/end time fail2ban older than 0.11 (no --with-time)
Banned IP still reaches a Docker service banaction blocks only INPUT – see Docker hosts
OIDC: redirect_uri mismatch provider must allow exactly <PUBLIC_URL>/auth/callback
OIDC: mismatching_state session cookie lost between redirect and callback: PUBLIC_URL host differs from the one in the browser, or https without Secure/proxy headers
OIDC: "account … is not allowed" user not in OIDC_ALLOWED_GROUPS; check that the provider sends the groups claim