Files
fail2ban-webui/docs/reverse-proxy.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

3.7 KiB
Raw Blame History

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://.