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>
5.1 KiB
Authentication
AUTH_MODE selects how users sign in. The container refuses to start without a working
configuration – there is no unauthenticated mode.
| Mode | Use when |
|---|---|
local |
one shared admin account is enough |
oidc |
you have an identity provider (Authentik, Keycloak, Authelia, Zitadel, …) |
local,oidc |
SSO with a local fallback account |
proxy |
your reverse proxy already authenticates (forward auth, basic auth, IP allow list) |
Every change (ban/unban) additionally needs the per-session CSRF token and a matching
Origin header, and every action is written to the audit log with the user name.
Local user
docker compose run --rm -it fail2ban-webui hash-password
AUTH_MODE=local
AUTH_USER=admin
AUTH_PASSWORD_HASH='scrypt:32768:8:1$…'
- Passwords need at least 12 characters; the hash is scrypt (Werkzeug).
- After
LOGIN_MAX_ATTEMPTS(5) failures a client IP is locked forLOGIN_LOCK_MINUTES(15). - Failed logins are logged as
LOGIN_FAILED user=… ip=…– see operations.md to let fail2ban ban them at the proxy. - Set
TRUSTED_PROXY_HOPS=1behind a reverse proxy, otherwise all clients appear with the proxy's address and share one lockout counter.
OpenID Connect
Requirements: PUBLIC_URL is set to the external https:// URL, and the provider allows
the redirect URI <PUBLIC_URL>/auth/callback.
The UI uses the authorization code flow with PKCE (S256) and a confidential client.
User name: OIDC_USERNAME_CLAIM (default preferred_username); groups:
OIDC_GROUPS_CLAIM (default groups).
Always restrict who may sign in with OIDC_ALLOWED_GROUPS and/or
OIDC_ALLOWED_USERS – or with an access policy in the provider. Without either, every
account that the provider lets use this client can ban IPs (a warning is logged at start).
Authentik
-
Applications → Providers → Create → OAuth2/OpenID Provider
- Name:
fail2ban-webui - Authorization flow:
default-provider-authorization-explicit-consent(or implicit) - Client type: Confidential – note Client ID and Client Secret
- Redirect URIs:
strict·https://fail2ban.example.org/auth/callback - Signing key: any certificate (e.g.
authentik Self-signed Certificate) - Advanced protocol settings → Scopes:
openid,email,profile(the default profile mapping contains thegroupsclaim)
- Name:
-
Applications → Applications → Create
- Name:
fail2ban-webui, Slug:fail2ban-webui, Provider: the one from step 1
- Name:
-
Directory → Groups → Create
fail2ban-adminsand add the users. -
Optional, recommended: in the application under Policy / Group / User Bindings bind the group
fail2ban-admins, so nobody else even reaches the consent screen. -
.env:PUBLIC_URL=https://fail2ban.example.org AUTH_MODE=oidc OIDC_ISSUER=https://auth.example.org/application/o/fail2ban-webui/ OIDC_CLIENT_ID=… OIDC_CLIENT_SECRET='…' OIDC_ALLOWED_GROUPS=fail2ban-admins OIDC_BUTTON_LABEL=Authentik -
docker compose up -d, openPUBLIC_URL, click Sign in with Authentik.
The issuer URL is shown in Authentik on the provider page (OpenID Configuration Issuer).
Other providers
| Provider | OIDC_ISSUER |
Groups |
|---|---|---|
| Keycloak | https://kc.example.org/realms/<realm> |
add a Group Membership mapper with claim name groups, "Full group path" off |
| Authelia | https://auth.example.org |
groups scope; set OIDC_SCOPES=openid email profile groups |
| Zitadel | https://<instance>.zitadel.cloud |
roles instead of groups – use OIDC_ALLOWED_USERS or a project role check in Zitadel |
Sign out
"Sign out" ends the session in the web UI. The session at the provider stays active, so the next "Sign in with …" usually logs in again without a password.
Reverse-proxy mode
The proxy does the authentication; the UI only checks where requests come from and, optionally, which user the proxy reports.
AUTH_MODE=proxy
TRUSTED_PROXY_HOPS=1
# only accept requests from the proxy (address as seen by the container)
AUTH_PROXY_ALLOWED_IPS=172.23.51.1/32
# optional: user name for the audit log, set by the proxy
AUTH_PROXY_HEADER=X-authentik-username
# optional: logout URL of the proxy/IdP
AUTH_PROXY_LOGOUT_URL=https://fail2ban.example.org/outpost.goauthentik.io/sign_out
AUTH_PROXY_ALLOWED_IPSis checked against the direct peer of the container, not againstX-Forwarded-For. If the proxy runs on the same Docker host and reaches the container via the published port, that is the network gateway (172.23.51.1in the example compose file); check withdocker compose logsafter a request.- With
AUTH_PROXY_HEADERset, requests without the header are rejected (401). Make sure the proxy removes that header from client requests – forward-auth integrations (Authentik, Authelia) do that. - Without
AUTH_PROXY_HEADERthe audit log shows the userproxy.
Examples for Nginx Proxy Manager, Traefik and Caddy are in reverse-proxy.md.