# 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=
/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://`.