# Configuration The web UI is configured with environment variables (`.env`, loaded via `env_file:`) and the host list `config/hosts.yaml`. A complete, commented template is [`examples/.env.example`](../examples/.env.example). Changes need `docker compose up -d` (which recreates the container). Values containing `$` (password hashes, some secrets) must be wrapped in **single quotes** in `.env`, otherwise Docker Compose expands them. ## Hosts – config/hosts.yaml ```yaml hosts: - name: web01 # shown in the UI: letters, digits, . _ - address: web01.example.org # DNS name or IP, reachable from the container port: 22 # optional, default 22 user: f2bweb # optional, default SSH_USER ``` After adding a host run `keyscan` again, then restart: ```bash docker compose run --rm -it fail2ban-webui keyscan docker compose run --rm fail2ban-webui check docker compose up -d --force-recreate ``` `F2B_HOSTS=name=address,name2=address2` (port 22, user `SSH_USER`) is still accepted for compatibility; entries from both are combined. ## General | Variable | Default | Meaning | |---|---|---| | `PUBLIC_URL` | – | external URL, e.g. `https://fail2ban.example.org`. Required for OIDC; with `https://` session cookies get the `Secure` flag | | `DEFAULT_LANG` | `en` | language until a user picks one in the dropdown (`en`, `de`, …) | | `TRUSTED_PROXY_HOPS` | `0` | number of reverse proxies whose `X-Forwarded-For`/`-Proto` headers are trusted. Behind one proxy: `1`. Wrong values make the audit log and the login lockout use the wrong client IP | | `SESSION_HOURS` | `12` | session lifetime | | `SESSION_COOKIE_SECURE` | from `PUBLIC_URL` | force the `Secure` cookie flag on/off | | `SECRET_KEY` | generated | key that signs session cookies; by default created once in `data/secret_key` | | `TZ` | `Europe/Vienna` (compose) | time zone of the log and audit timestamps | | `CONFIG_FILE` | `/config/hosts.yaml` | path of the host list inside the container | | `DATA_DIR` | `/data` | data directory inside the container | ## Authentication See [authentication.md](authentication.md) for the complete setup. | Variable | Default | Meaning | |---|---|---| | `AUTH_MODE` | `local` | `local`, `oidc`, `local,oidc` or `proxy` | | `AUTH_USER` | `admin` | local user name | | `AUTH_PASSWORD_HASH` | – | scrypt hash from `hash-password` (single quotes!) | | `AUTH_PASSWORD_HASH_FILE` | – | alternative: file containing the hash (e.g. a Docker secret) | | `LOGIN_MAX_ATTEMPTS` | `5` | failed logins per client IP before the lockout | | `LOGIN_LOCK_MINUTES` | `15` | duration of the lockout window | | `OIDC_ISSUER` | – | issuer URL; the discovery document is read from `/.well-known/openid-configuration` | | `OIDC_DISCOVERY_URL` | – | full discovery URL instead of `OIDC_ISSUER` | | `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` | – | client credentials | | `OIDC_SCOPES` | `openid email profile` | requested scopes | | `OIDC_USERNAME_CLAIM` | `preferred_username` | claim shown as user name (falls back to `email`, then `sub`) | | `OIDC_GROUPS_CLAIM` | `groups` | claim with the group list | | `OIDC_ALLOWED_GROUPS` | – | only members of these groups may sign in (comma separated) | | `OIDC_ALLOWED_USERS` | – | only these user names may sign in | | `OIDC_BUTTON_LABEL` | `SSO` | text on the login button ("Sign in with …") | | `AUTH_PROXY_ALLOWED_IPS` | – | proxy mode: only accept requests from these addresses/networks | | `AUTH_PROXY_HEADER` | – | proxy mode: header with the user name, e.g. `X-authentik-username` | | `AUTH_PROXY_LOGOUT_URL` | – | proxy mode: target of the "Sign out" button | ## Agents (SSH) | Variable | Default | Meaning | |---|---|---| | `SSH_USER` | `f2bweb` | default user for hosts without `user:` | | `SSH_KEY` | `/data/ssh/id_ed25519` | private key | | `KNOWN_HOSTS` | `/data/ssh/known_hosts` | pinned host keys (written by `keyscan`) | | `SSH_TIMEOUT` | `40` | seconds per agent call | | `CACHE_SECONDS` | `15` | how long the ban list is cached between page refreshes | | `MANUAL_JAIL` | `manual-block` | jail name shown in the UI hint – the agent decides the real jail (`/etc/f2b-webctl.conf`) | | `AUDIT_LOG` | `/data/audit.log` | audit log (JSON lines) | The SSH client ignores the system `ssh_config`, uses only the configured key, never asks questions and refuses unknown host keys. ## GeoIP | Variable | Default | Meaning | |---|---|---| | `GEOIP_AUTO_DOWNLOAD` | `0` | `1`: download the free [DB-IP Lite](https://db-ip.com/db/lite.php) city database (CC BY 4.0) and refresh it monthly | | `GEOIP_DB` | `/data/geoip/dbip-city-lite.mmdb` | path of the database – any MaxMind-format city database works (e.g. GeoLite2 City) | With a city database the UI also shows the **origin map** (country-only databases have no coordinates). The map is bundled with the app (Natural Earth, public domain) – no external tile server is contacted. Without a database the "Origin" column stays empty and the map is hidden. ## Loki and Grafana With Loki the UI gets a **History** button per IP showing the matching log lines of the last days; with Grafana every ban gets a link to Explore. | Variable | Default | Meaning | |---|---|---| | `LOKI_URL` | – | e.g. `http://loki:3100`; empty = history disabled | | `LOKI_SELECTOR` | `{job=~".+"}` | LogQL stream selector for the search, e.g. `{job=~"fail2ban\|sshd\|nginx"}` | | `LOKI_DAYS` | `7` | how far back to search | | `LOKI_LIMIT` | `50` | maximum lines | | `LOKI_USER` / `LOKI_PASSWORD` | – | basic auth | | `LOKI_TENANT` | – | `X-Scope-OrgID` for multi-tenant Loki | | `GRAFANA_URL` | – | e.g. `https://grafana.example.org` | | `GRAFANA_DASHBOARD` | – | path of a dashboard for the header link, e.g. `/d/abc123/security` | | `GRAFANA_LOKI_UID` | `loki` | UID of the Loki data source in Grafana (for Explore links) | Example – exclude the UI's own access log from the search: ```ini LOKI_SELECTOR={job=~"fail2ban|ufw|nginx|sshd", domain!="fail2ban.example.org"} ``` ## Agent settings Protected addresses and the manual jail are configured **on each host** in `/etc/f2b-webctl.conf` – see [agent.md](agent.md#configuration--etcf2b-webctlconf).