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>
122 lines
3.7 KiB
Markdown
122 lines
3.7 KiB
Markdown
# 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`:
|
||
|
||
```ini
|
||
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:
|
||
|
||
```nginx
|
||
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
|
||
|
||
```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:
|
||
|
||
```yaml
|
||
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)
|
||
|
||
```yaml
|
||
- traefik.http.routers.f2bwebui.middlewares=authentik@docker
|
||
```
|
||
|
||
```ini
|
||
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
|
||
|
||
```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
|
||
|
||
```bash
|
||
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://`.
|