Piclaw's web UI can run behind a reverse proxy or tunnel.
Use this guide 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
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.
Set:
PICLAW_TRUST_PROXY=1or 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
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
Your proxy should forward either:
- the standard
Forwardedheader
or:
X-Forwarded-HostX-Forwarded-Proto
Recommended extras:
X-Forwarded-PortX-Forwarded-ForX-Real-IP
A typical cloudflared setup can keep piclaw listening only on local HTTP while Cloudflare terminates TLS publicly.
PICLAW_WEB_HOST=127.0.0.1
PICLAW_WEB_PORT=8080
PICLAW_TRUST_PROXY=1
# Leave PICLAW_WEB_EXTERNAL_URL unset when the same instance must work
# through both a public proxy hostname and direct LAN access.If you run inside the standard container, keep the container/web binding consistent with your deployment model. The tunnel connects to piclaw over an internal hop while the browser uses the public HTTPS hostname.
Piclaw remembers the browser origin from forwarded headers on authenticated web requests. That remembered origin is used for generated web links when PICLAW_WEB_EXTERNAL_URL is unset, so public proxy access and LAN access can coexist without pinning the instance to one hostname.
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:404Cloudflare 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.
If WebAuthn/passkeys are enabled, the browser hostname used during enrolment/login must match the public hostname users actually visit. An account can have multiple credentials; keys registered for another RP ID do not satisfy the current hostname's factor checks. Enable proxy trust behind the tunnel.
Only accept forwarded headers from your trusted proxy: restrict direct backend access and have the proxy overwrite client-supplied forwarding headers. Preserve browser Origin and Cookie headers. In the gated family APIs, account mutations/invitation redemption require a matching Origin, and enrolment cookies use Secure/HttpOnly/SameSite=Strict. Use HTTPS for browser ceremonies. Do not strip private/no-store response headers or cache account, invitation or auth responses.
This is not a guide to deploying family mode; multi-user startup is still disabled.
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.
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;
}
}If users connect directly to piclaw with no proxy/tunnel in front:
- leave
PICLAW_TRUST_PROXY=0 - do not inject forwarded headers unnecessarily
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.
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
Check:
X-Forwarded-HostX-Forwarded-Proto- optional
X-Forwarded-Port - whether the proxy is rewriting
Hostunexpectedly - whether
PICLAW_WEB_EXTERNAL_URLis set; if set, it deliberately overrides the remembered forwarded/browser origin
Check:
X-Forwarded-ForX-Real-IP- whether there is more than one proxy hop in front of piclaw
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