docs/gateway/trusted-proxy-auth.md
Use trusted-proxy auth mode when:
1008 unauthorized errors because browsers can't pass tokens in WS payloads.When gateway.auth.mode = "trusted-proxy" is active and the request passes trusted-proxy checks, Control UI WebSocket sessions can connect without device pairing identity.
Implications:
allowUsers become the effective access control.gateway.trustedProxies + firewall).{
gateway: {
// Trusted-proxy auth expects requests from a non-loopback trusted proxy source by default
bind: "lan",
// CRITICAL: Only add your proxy's IP(s) here
trustedProxies: ["10.0.0.1", "172.17.0.1"],
auth: {
mode: "trusted-proxy",
trustedProxy: {
// Header containing authenticated user identity (required)
userHeader: "x-forwarded-user",
// Optional: headers that MUST be present (proxy verification)
requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"],
// Optional: restrict to specific users (empty = allow all)
allowUsers: ["[email protected]", "[email protected]"],
// Optional: allow a same-host loopback proxy after explicit opt-in
allowLoopback: false,
},
},
},
}
127.0.0.1, ::1, loopback CIDRs) by default.gateway.auth.trustedProxy.allowLoopback = true and include the loopback address in gateway.trustedProxies.allowLoopback trusts local processes on the Gateway host to the same degree as the reverse proxy. Enable it only when the Gateway is still firewalled from direct remote access and the local proxy strips or overwrites client-supplied identity headers.gateway.auth.password / OPENCLAW_GATEWAY_PASSWORD, not trusted-proxy identity headers.gateway.controlUi.allowedOrigins.X-Forwarded-For / X-Forwarded-Host / X-Forwarded-Proto headers pointing at a non-local origin, that evidence disqualifies local-direct password fallback and device-identity gating. With allowLoopback: true, trusted-proxy auth can still accept the request as a same-host proxy request, while requiredHeaders and allowUsers continue to apply.Use one TLS termination point and apply HSTS there.
<Tabs> <Tab title="Proxy TLS termination (recommended)"> When your reverse proxy handles HTTPS for `https://control.example.com`, set `Strict-Transport-Security` at the proxy for that domain.- Good fit for internet-facing deployments.
- Keeps certificate + HTTP hardening policy in one place.
- OpenClaw can stay on loopback HTTP behind the proxy.
Example header value:
```text
Strict-Transport-Security: max-age=31536000; includeSubDomains
```
```json5
{
gateway: {
tls: { enabled: true },
http: {
securityHeaders: {
strictTransportSecurity: "max-age=31536000; includeSubDomains",
},
},
},
}
```
`strictTransportSecurity` accepts a string header value, or `false` to disable explicitly.
max-age=300) while validating traffic.max-age=31536000) only after confidence is high.includeSubDomains only if every subdomain is HTTPS-ready.```json5
{
gateway: {
bind: "lan",
trustedProxies: ["10.0.0.1"], // Pomerium's IP
auth: {
mode: "trusted-proxy",
trustedProxy: {
userHeader: "x-pomerium-claim-email",
requiredHeaders: ["x-pomerium-jwt-assertion"],
},
},
},
}
```
Pomerium config snippet:
```yaml
routes:
- from: https://openclaw.example.com
to: http://openclaw-gateway:18789
policy:
- allow:
or:
- email:
is: [email protected]
pass_identity_headers: true
```
```json5
{
gateway: {
bind: "lan",
trustedProxies: ["10.0.0.1"], // Caddy/sidecar proxy IP
auth: {
mode: "trusted-proxy",
trustedProxy: {
userHeader: "x-forwarded-user",
},
},
},
}
```
Caddyfile snippet:
```
openclaw.example.com {
authenticate with oauth2_provider
authorize with policy1
reverse_proxy openclaw:18789 {
header_up X-Forwarded-User {http.auth.user.email}
}
}
```
```json5
{
gateway: {
bind: "lan",
trustedProxies: ["10.0.0.1"], // nginx/oauth2-proxy IP
auth: {
mode: "trusted-proxy",
trustedProxy: {
userHeader: "x-auth-request-email",
},
},
},
}
```
nginx config snippet:
```nginx
location / {
auth_request /oauth2/auth;
auth_request_set $user $upstream_http_x_auth_request_email;
proxy_pass http://openclaw:18789;
proxy_set_header X-Auth-Request-Email $user;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
```
OpenClaw rejects ambiguous configurations where both a gateway.auth.token (or OPENCLAW_GATEWAY_TOKEN) and trusted-proxy mode are active at the same time. Mixed token configs can cause loopback requests to silently authenticate on the wrong auth path.
If you see a mixed_trusted_proxy_token error on startup:
gateway.auth.mode to "token" if you intend token-based auth.Loopback trusted-proxy identity headers still fail closed: same-host callers are not silently authenticated as proxy users. Internal OpenClaw callers that bypass the proxy may authenticate with gateway.auth.password / OPENCLAW_GATEWAY_PASSWORD instead. Token fallback remains intentionally unsupported in trusted-proxy mode.
Trusted-proxy auth is an identity-bearing HTTP mode, so callers may optionally declare operator scopes with x-openclaw-scopes.
Examples:
x-openclaw-scopes: operator.readx-openclaw-scopes: operator.read,operator.writex-openclaw-scopes: operator.admin,operator.writeBehavior:
x-openclaw-scopes is absent, their runtime scope falls back to operator.write.gateway.controlUi.allowedOrigins (or deliberate Host-header fallback mode) even after trusted-proxy auth succeeds.Practical rule: send x-openclaw-scopes explicitly when you want a trusted-proxy request to be narrower than the defaults, or when a gateway-auth plugin route needs something stronger than write scope.
Before enabling trusted-proxy auth, verify:
gateway.auth.trustedProxy.allowLoopback is explicitly enabled for a same-host proxy.x-forwarded-* headers from clients.gateway.controlUi.allowedOrigins.gateway.auth.token and gateway.auth.mode: "trusted-proxy".gateway.auth.password for internal direct callers, keep the Gateway port firewalled so non-proxy remote clients cannot reach it directly.openclaw security audit will flag trusted-proxy auth with a critical severity finding. This is intentional — it's a reminder that you're delegating security to your proxy setup.
The audit checks for:
gateway.trusted_proxy_auth warning/critical remindertrustedProxies configurationuserHeader configurationallowUsers (allows any authenticated user)allowLoopback for same-host proxy sources- Is the proxy IP correct? (Docker container IPs can change.)
- Is there a load balancer in front of your proxy?
- Use `docker inspect` or `kubectl get pods -o wide` to find actual IPs.
Check:
- Is the proxy connecting from `127.0.0.1` / `::1`?
- Are you trying to use trusted-proxy auth with a same-host loopback reverse proxy?
Fix:
- Prefer token/password auth for internal same-host clients that do not go through the proxy, or
- Route through a non-loopback trusted proxy address and keep that IP in `gateway.trustedProxies`, or
- For a deliberate same-host reverse proxy, set `gateway.auth.trustedProxy.allowLoopback = true`, keep the loopback address in `gateway.trustedProxies`, and make sure the proxy strips or overwrites identity headers.
- Is your proxy configured to pass identity headers?
- Is the header name correct? (case-insensitive, but spelling matters)
- Is the user actually authenticated at the proxy?
- Your proxy configuration for those specific headers.
- Whether headers are being stripped somewhere in the chain.
Check:
- `gateway.controlUi.allowedOrigins` includes the exact browser origin.
- You are not relying on wildcard origins unless you intentionally want allow-all behavior.
- If you intentionally use Host-header fallback mode, `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true` is set deliberately.
- Supports WebSocket upgrades (`Upgrade: websocket`, `Connection: upgrade`).
- Passes the identity headers on WebSocket upgrade requests (not just HTTP).
- Doesn't have a separate auth path for WebSocket connections.
If you're moving from token auth to trusted-proxy:
<Steps> <Step title="Configure the proxy"> Configure your proxy to authenticate users and pass headers. </Step> <Step title="Test the proxy independently"> Test the proxy setup independently (curl with headers). </Step> <Step title="Update OpenClaw config"> Update OpenClaw config with trusted-proxy auth. </Step> <Step title="Restart the Gateway"> Restart the Gateway. </Step> <Step title="Test WebSocket"> Test WebSocket connections from the Control UI. </Step> <Step title="Audit"> Run `openclaw security audit` and review findings. </Step> </Steps>