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>
3.7 KiB
Reverse proxy
The container serves plain HTTP on port 8080 (published as 127.0.0.1:8080 in the
example). Put a reverse proxy with TLS in front of it and set in .env:
PUBLIC_URL=https://fail2ban.example.org
TRUSTED_PROXY_HOPS=1
The proxy must pass Host unchanged and set X-Forwarded-For and X-Forwarded-Proto –
all examples below do. Restricting access to known IP addresses at the proxy is a cheap
extra layer and strongly recommended.
Nginx Proxy Manager
Hosts → Proxy Hosts → Add Proxy Host
| Field | Value |
|---|---|
| Domain Names | fail2ban.example.org |
| Scheme / Forward Hostname / Port | http · IP of the Docker host (or 127.0.0.1 if NPM runs with host networking) · 8080 |
| Block Common Exploits | on |
| SSL | request a certificate, Force SSL, HTTP/2, HSTS |
| Access List | optional: an access list with your IP addresses (Satisfy any off) |
If NPM runs in another Docker network on the same host, bind the UI port to the host's
LAN address (e.g. "192.168.1.10:8080:8080") or attach the UI to NPM's network and use
fail2ban-webui:8080 as target.
Response headers belong into Advanced → Custom Nginx Configuration; NPM's image includes the headers-more module, which also applies them to proxied responses:
more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
(The app sets CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy and
Cache-Control itself.)
Caddy
fail2ban.example.org {
@allowed remote_ip 203.0.113.0/24 2001:db8::/32
handle @allowed {
reverse_proxy 127.0.0.1:8080
}
respond 403
}
Caddy sets X-Forwarded-For/-Proto automatically.
Traefik (Docker labels)
Attach the container to Traefik's network and add labels instead of publishing the port:
services:
fail2ban-webui:
# … as in the example compose file, without "ports:"
labels:
- traefik.enable=true
- traefik.http.routers.f2bwebui.rule=Host(`fail2ban.example.org`)
- traefik.http.routers.f2bwebui.entrypoints=websecure
- traefik.http.routers.f2bwebui.tls.certresolver=letsencrypt
- traefik.http.routers.f2bwebui.middlewares=f2bwebui-ipallow
- traefik.http.middlewares.f2bwebui-ipallow.ipallowlist.sourcerange=203.0.113.0/24
- traefik.http.services.f2bwebui.loadbalancer.server.port=8080
networks:
- fail2ban-webui.dockernetwork.local
- traefik
Traefik with Authentik forward auth (proxy mode)
- traefik.http.routers.f2bwebui.middlewares=authentik@docker
AUTH_MODE=proxy
AUTH_PROXY_HEADER=X-authentik-username
AUTH_PROXY_ALLOWED_IPS=<address of Traefik in the shared network>/32
AUTH_PROXY_LOGOUT_URL=https://fail2ban.example.org/outpost.goauthentik.io/sign_out
nginx
server {
listen 443 ssl;
http2 on;
server_name fail2ban.example.org;
ssl_certificate /etc/letsencrypt/live/fail2ban.example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/fail2ban.example.org/privkey.pem;
allow 203.0.113.0/24;
deny all;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 90s; # several slow agents can take a while
}
}
Check
curl -sI https://fail2ban.example.org/login | grep -iE '^(HTTP|content-security|set-cookie)'
Expect HTTP/2 200, the Content-Security-Policy header and a session cookie with
Secure; HttpOnly; SameSite=Lax. A cookie without Secure means PUBLIC_URL does not
start with https://.