Reverse proxy and tunnel setup #
This page documents how to run piclaw's web UI behind a reverse proxy or tunnel.
Use this guidance when the browser reaches piclaw through an external hostname such as:
- Cloudflare Tunnel
- Caddy / Nginx / Traefik TLS termination
- a load balancer or ingress controller
- any setup where piclaw itself only sees an internal
http://host:porthop
Why this matters #
When piclaw is proxied, the external browser origin may differ from the direct origin seen by the app. Without proxy trust, that can break:
- browser-origin / CSRF checks on state-changing requests
- WebAuthn / passkey flows
- absolute URL generation for web-channel links
- client IP derivation for logging and rate limiting
piclaw only trusts forwarded proxy headers when you enable proxy trust explicitly.
Enable proxy trust #
Set:
PICLAW_TRUST_PROXY=1
or in .piclaw/config.json:
{
"web": {
"trustProxy": true
}
}
This maps to the runtime config consumed by:
runtime/src/utils/request-client.tsruntime/src/channels/web/http/security.tsruntime/src/channels/web/auth/request-origin.ts
Forwarded headers piclaw expects #
When PICLAW_TRUST_PROXY=1 is enabled, piclaw resolves the external origin using this data:
X-Forwarded-Proto(preferred) orForwarded: proto=...X-Forwarded-Host(preferred) orForwarded: host=...- optional
X-Forwarded-Port - falls back to the request URL /
Hostheader when no forwarded values are present
For client-IP derivation it uses:
X-Forwarded-ForX-Real-IP- otherwise
unknown
Minimum required headers #
Your proxy should forward either:
- the standard
Forwardedheader
or:
X-Forwarded-HostX-Forwarded-Proto
Recommended extras:
X-Forwarded-PortX-Forwarded-ForX-Real-IP
Cloudflare Tunnel example #
A typical cloudflared setup can keep piclaw listening only on local HTTP while Cloudflare terminates TLS publicly.
Piclaw side #
PICLAW_WEB_HOST=127.0.0.1
PICLAW_WEB_PORT=8080
PICLAW_TRUST_PROXY=1
If you run inside the standard container, keep the container/web binding consistent with your deployment model. The key point is that the tunnel connects to piclaw over an internal hop while the browser uses the public HTTPS hostname.
cloudflared config example #
tunnel: YOUR_TUNNEL_ID
credentials-file: /etc/cloudflared/YOUR_TUNNEL_ID.json
ingress:
- hostname: piclaw.example.com
service: http://127.0.0.1:8080
- service: http_status:404
Cloudflare Tunnel will supply the external host/proto context. With PICLAW_TRUST_PROXY=1, piclaw will treat the browser origin as https://piclaw.example.com instead of the internal http://127.0.0.1:8080 hop.
Notes for passkeys #
If WebAuthn/passkeys are enabled, the browser hostname used during enrolment/login must match the public hostname users actually visit. That is exactly why proxy trust must be enabled behind the tunnel.
Caddy example #
piclaw.example.com {
reverse_proxy 127.0.0.1:8080
}
Caddy already forwards the usual proxy headers needed for this flow. Enable PICLAW_TRUST_PROXY=1 on the piclaw side.
Nginx example #
server {
listen 443 ssl http2;
server_name piclaw.example.com;
ssl_certificate /etc/letsencrypt/live/piclaw.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/piclaw.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-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
}
}
Direct deployments #
If users connect directly to piclaw with no proxy/tunnel in front:
- leave
PICLAW_TRUST_PROXY=0 - do not inject forwarded headers unnecessarily
Troubleshooting #
Browser POSTs fail with origin / CSRF errors #
Check:
PICLAW_TRUST_PROXY=1- forwarded host/proto headers are present
- the public hostname in the browser matches the forwarded host
Symptoms usually include failed posting, uploads, or other state-changing requests behind a proxy.
Passkey / WebAuthn login fails only behind the proxy #
Check:
- the browser is using the final public hostname
PICLAW_TRUST_PROXY=1is enabled- the proxy forwards host/proto correctly
- you are not mixing multiple public hostnames during enrolment/login
Generated links point to the wrong host or http:// #
Check:
X-Forwarded-HostX-Forwarded-Proto- optional
X-Forwarded-Port - whether the proxy is rewriting
Hostunexpectedly
Client IPs all look wrong #
Check:
X-Forwarded-ForX-Real-IP- whether there is more than one proxy hop in front of piclaw
Operational checklist #
Before declaring a proxied deployment healthy, verify:
- login works
- posting works
- uploads work
- workspace writes work
- SSE/web streaming stays connected
- passkeys work, if enabled
- generated links use the public origin