-
Notifications
You must be signed in to change notification settings - Fork 0
Reverse Proxy Web GUI
Use this guide when exposing the web admin UI through a reverse proxy.
- Terminate HTTPS at the proxy.
- Forward the original host/protocol headers correctly.
- Keep CSRF and same-origin protections enabled.
- Avoid direct public exposure of the container port.
- Optionally keep the bot's built-in HTTPS listener on
8081for local/TLS fallback testing.
- Single-host local reverse proxy (Nginx/Caddy/Apache/HAProxy)
- Container-native edge routing (Traefik)
- Nginx Proxy Manager UI-based configuration
- DNS-proxied frontends (for example Cloudflare DNS/proxy) in front of your reverse proxy
Set these values in .env:
WEB_ENABLED=true
WEB_BIND_HOST=0.0.0.0
WEB_PORT=8080
WEB_HTTPS_ENABLED=true
WEB_HTTPS_PORT=8081
WEB_PUBLIC_BASE_URL=https://discord-admin.example.com/
WEB_TRUST_PROXY_HEADERS=true
WEB_SESSION_COOKIE_SECURE=true
WEB_SESSION_COOKIE_SAMESITE=Lax
WEB_ENFORCE_CSRF=true
WEB_ENFORCE_SAME_ORIGIN_POSTS=trueNotes:
-
WEB_PUBLIC_BASE_URLmust match the external URL users open in their browser. - Keep a trailing slash on
WEB_PUBLIC_BASE_URLfor consistency. - Leave
WEB_TRUST_PROXY_HEADERS=trueonly when the proxy is trusted and under your control. - If no TLS files exist, the bot generates a self-signed certificate in
${DATA_DIR}/ssl/. - Replace
${DATA_DIR}/ssl/tls.crtand${DATA_DIR}/ssl/tls.keywith your own certificate and key if you want the built-in HTTPS listener to be browser-trusted.
- Bind the app to an internal network only.
- Expose the web admin service to the proxy, not directly to the internet.
- If you must publish a host port, restrict it via firewall or localhost bind.
Example (localhost-only mapping):
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:8081:8081"Equivalent .env values for that mapping:
WEB_HTTP_PUBLISH=127.0.0.1:8080
WEB_HTTPS_PUBLISH=127.0.0.1:8081If your reverse proxy is on another machine, disable explicit host/IP pinning or publish on the Docker host LAN IP instead:
# Disabled explicit host/IP pinning:
# WEB_HTTP_PUBLISH=8080
# WEB_HTTPS_PUBLISH=8081
# Or pin to the Docker host LAN IP:
# WEB_HTTP_PUBLISH=192.168.1.50:8080
# WEB_HTTPS_PUBLISH=192.168.1.50:8081Restrict access to those ports with a firewall so only the proxy machine can reach them.
Replace discord-admin.example.com with your real domain.
server {
listen 80;
server_name discord-admin.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name discord-admin.example.com;
ssl_certificate /etc/letsencrypt/live/discord-admin.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/discord-admin.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 60s;
proxy_connect_timeout 10s;
}
}Minimum NPM settings:
- Domain Names:
discord-admin.example.com - Scheme/Forward Hostname:
http-> upstream host running bot web service - Forward Port:
8080(or your mapped local port) - Enable Websockets Support
- Enable Block Common Exploits
- SSL tab: Force SSL + HTTP/2 + HSTS (and include subdomains only if appropriate for your domain design)
Advanced header requirement:
- Preserve
Hostand forwardX-Forwarded-*headers. - Most NPM defaults handle this; custom advanced config can be used if needed.
discord-admin.example.com {
reverse_proxy 127.0.0.1:8080 {
header_up Host {host}
header_up X-Forwarded-Host {host}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote_host}
header_up X-Real-IP {remote_host}
}
}services:
bot:
labels:
- "traefik.enable=true"
- "traefik.http.routers.bot-web.rule=Host(`discord-admin.example.com`)"
- "traefik.http.routers.bot-web.entrypoints=websecure"
- "traefik.http.routers.bot-web.tls=true"
- "traefik.http.services.bot-web.loadbalancer.server.port=8080"
- "traefik.http.middlewares.bot-web-headers.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.bot-web.middlewares=bot-web-headers"If you terminate TLS at Traefik, keep router entrypoint on your HTTPS entrypoint (commonly websecure).
If DNS is proxied by Cloudflare (or similar CDN proxy):
- Keep origin proxy to app over trusted private path where possible.
- Ensure final browser origin still matches
WEB_PUBLIC_BASE_URL. - Keep
X-Forwarded-Proto=httpsat the app boundary. - Avoid mixing multiple public hostnames unless each is explicitly allowed by configuration.
Enable modules:
proxyproxy_httpsslheadersrewrite
Virtual host example:
<VirtualHost *:80>
ServerName discord-admin.example.com
RewriteEngine On
RewriteRule ^/(.*)$ https://discord-admin.example.com/$1 [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName discord-admin.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/discord-admin.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/discord-admin.example.com/privkey.pem
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Host "%{HTTP_HOST}s"
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>frontend https_in
bind *:443 ssl crt /etc/letsencrypt/live/discord-admin.example.com/haproxy.pem
mode http
option forwardfor
http-request set-header X-Forwarded-Proto https
http-request set-header X-Forwarded-Host %[req.hdr(host)]
use_backend bot_web if { hdr(host) -i discord-admin.example.com }
backend bot_web
mode http
server bot_local 127.0.0.1:8080 checkIf login fails with Blocked request due to origin policy.:
- Confirm
WEB_PUBLIC_BASE_URLmatches the exact external host users access. - Confirm proxy forwards
HostandX-Forwarded-Host. - Confirm
WEB_TRUST_PROXY_HEADERS=true. - Confirm browser is using the same domain as
WEB_PUBLIC_BASE_URL.
If secure cookies do not persist:
- Confirm users access the site over
https://. - Keep
WEB_SESSION_COOKIE_SECURE=true. - Confirm
X-Forwarded-Protois passed ashttps.
If login POST returns 302 then immediately loads /login again:
- Confirm
WEB_PUBLIC_BASE_URLmatches the exact host users open. - Confirm proxy forwards
Host,X-Forwarded-Host, andX-Forwarded-Proto. - Use
WEB_SESSION_COOKIE_SAMESITE=Laxfor proxy compatibility (switch toStrictafter stable if desired). - Confirm the upstream target in
proxy_passishttp://...:8080(nothttps://to the container).
If browser console warns that Cross-Origin-Opener-Policy was ignored on login:
- Serve the web UI over
https://(or localhost/loopback for local testing). - Verify proxy redirect from
http://tohttps://is active. - Verify users are not accessing an internal plain-HTTP URL directly.
- Login works through proxy.
- POST actions (save settings, create users, update profile) succeed.
- No origin-policy errors in logs.
- Cookies show
HttpOnly,Secure, andSameSite=Strict. - Direct container port is not publicly reachable.