Skip to content

Configure OpenClaw Trusted Proxy Auth for Secure Access

Managing authentication can feel like a chore, especially when you already have a solid identity provider sitting in front of your stack. If you’ve ever struggled with WebSocket connection errors or felt the friction of managing separate tokens for every single internal tool, you’re in the right place.

Trusted Proxy Auth lets you hand off the heavy lifting of identity verification to your reverse proxy. It’s a great way to keep your setup clean, but because it’s a security-sensitive feature, you need to get the details right.

You should consider the trusted-proxy auth mode if:

  • You run OpenClaw behind an identity-aware proxy like Pomerium, Caddy with OAuth, nginx with oauth2-proxy, or Traefik with forward auth.
  • Your proxy handles all authentication and passes user identity through headers.
  • You’re in a Kubernetes or container environment where the proxy is the only way to reach the Gateway.
  • You’re seeing WebSocket 1008 unauthorized errors because browsers can’t pass tokens in WebSocket payloads.

Avoid this mode if:

  • Your proxy doesn’t actually authenticate users and just handles TLS or load balancing.
  • There is any way to reach the Gateway that bypasses the proxy, such as firewall holes or direct internal network access.
  • You aren’t sure if your proxy correctly strips or overwrites forwarded headers from clients.
  • You only need personal single-user access; in that case, Tailscale Serve with loopback is often a simpler choice.

The process is straightforward:

  1. Your reverse proxy authenticates users via OAuth, OIDC, SAML, or another method.
  2. The proxy adds a header containing the authenticated user identity (like x-forwarded-user: nick@example.com).
  3. OpenClaw checks that the request came from a trusted proxy IP defined in your config.
  4. OpenClaw pulls the user identity from the specific header you configured.
  5. If the IP and headers check out, the request is authorized.

When you activate gateway.auth.mode = "trusted-proxy" and a request passes the checks, Control UI WebSocket sessions can connect without needing a device pairing identity.

This has a few important effects:

  • Pairing is no longer the main gate for Control UI access.
  • Your reverse proxy auth policy and the allowUsers list become your primary access controls.
  • You must keep gateway ingress locked down to trusted proxy IPs only using gateway.trustedProxies and your firewall.
{
gateway: {
// Use loopback for same-host proxy setups; use lan/custom for remote proxy hosts
bind: "loopback",
// 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: ["nick@example.com", "admin@company.org"],
},
},
},
}

If your gateway.bind is set to loopback, make sure to include a loopback proxy address in gateway.trustedProxies (like 127.0.0.1, ::1, or an equivalent CIDR).

FieldRequiredDescription
gateway.trustedProxiesYesArray of proxy IP addresses to trust. Requests from other IPs are rejected.
gateway.auth.modeYesMust be "trusted-proxy"
gateway.auth.trustedProxy.userHeaderYesHeader name containing the authenticated user identity
gateway.auth.trustedProxy.requiredHeadersNoAdditional headers that must be present for the request to be trusted
gateway.auth.trustedProxy.allowUsersNoAllowlist of user identities. Empty means allow all authenticated users.

I recommend using one TLS termination point and applying HSTS there.

Section titled “Recommended pattern: proxy TLS termination”

When your reverse proxy handles HTTPS for https://control.example.com, you should set Strict-Transport-Security at the proxy for that domain.

  • This is a good fit for internet-facing deployments.
  • It keeps your certificate and HTTP hardening policy in one spot.
  • OpenClaw can stay on loopback HTTP behind the proxy.

Example header value:

Strict-Transport-Security: max-age=31536000; includeSubDomains

If OpenClaw serves HTTPS directly without a TLS-terminating proxy, use this config:

{
gateway: {
tls: { enabled: true },
http: {
securityHeaders: {
strictTransportSecurity: "max-age=31536000; includeSubDomains",
},
},
},
}

The strictTransportSecurity field takes a string header value. You can also set it to false to disable it explicitly.

  • Start with a short max age like max-age=300 while you validate traffic.
  • Only increase to long-lived values like max-age=31536000 once you are confident.
  • Only add includeSubDomains if every subdomain is ready for HTTPS.
  • Use preload only if you intend to meet the requirements for your full domain set.
  • Local development on loopback doesn’t need HSTS.

Pomerium passes identity in x-pomerium-claim-email and a JWT in x-pomerium-jwt-assertion.

{
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:

routes:
- from: https://openclaw.example.com
to: http://openclaw-gateway:18789
policy:
- allow:
or:
- email:
is: nick@example.com
pass_identity_headers: true

Caddy with the caddy-security plugin can handle authentication and pass identity headers.

{
gateway: {
bind: "lan",
trustedProxies: ["127.0.0.1"], // Caddy's IP (if on same host)
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}
}
}

In this setup, oauth2-proxy authenticates users and passes identity in x-auth-request-email.

{
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:

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";
}
{
gateway: {
bind: "lan",
trustedProxies: ["172.17.0.1"], // Traefik container IP
auth: {
mode: "trusted-proxy",
trustedProxy: {
userHeader: "x-forwarded-user",
},
},
},
}

OpenClaw will reject configurations where both a gateway.auth.token (or OPENCLAW_GATEWAY_TOKEN) and trusted-proxy mode are active. This prevents loopback requests from accidentally using the wrong authentication path.

If you run into a mixed_trusted_proxy_token error:

  • Remove the shared token if you want to use trusted-proxy mode.
  • Switch gateway.auth.mode to "token" if you prefer token-based auth.

Loopback trusted-proxy auth is designed to fail closed. Callers on the same host must provide the configured identity headers through a trusted proxy rather than being authenticated automatically.

Before you flip the switch on trusted-proxy auth, verify these points:

  • Proxy is the only path: Your Gateway port is firewalled from everything except your proxy.
  • trustedProxies is minimal: You only listed actual proxy IPs, not broad subnets.
  • Proxy strips headers: Your proxy overwrites x-forwarded-* headers from clients instead of just appending to them.
  • TLS termination: Your proxy handles TLS so users connect via HTTPS.
  • allowUsers is set: It is safer to restrict access to known users rather than allowing anyone who is authenticated.
  • No mixed token config: You haven’t set both gateway.auth.token and gateway.auth.mode: "trusted-proxy".

Running openclaw security audit will show a critical severity finding for trusted-proxy auth. This is by design to remind you that security depends on your proxy setup.

The audit looks for:

  • Missing trustedProxies settings.
  • Missing userHeader settings.
  • An empty allowUsers list (which lets any authenticated user in).

This means the request came from an IP not listed in gateway.trustedProxies.

  • Check if the proxy IP is correct (Docker container IPs change often).
  • See if there is a load balancer in front of your proxy.
  • Use docker inspect or kubectl get pods -o wide to find the real IPs.

The user header was empty or not there at all.

  • Verify your proxy is configured to pass identity headers.
  • Check that the header name is spelled correctly.
  • Ensure the user is actually authenticated at the proxy level.

A required header was missing.

  • Check your proxy config for those specific headers.
  • Look for anything in the chain that might be stripping headers.

The user is authenticated but isn’t in your allowUsers list. You’ll need to add them or clear the allowlist.

If WebSockets aren’t working, make sure your proxy:

  • Supports WebSocket upgrades (Upgrade: websocket, Connection: upgrade).
  • Passes identity headers on the upgrade request.
  • Doesn’t use a separate auth path for WebSocket connections.

Moving from tokens to a trusted proxy? Follow these steps:

  1. Set up your proxy to authenticate users and pass headers.
  2. Test the proxy setup on its own using curl with headers.
  3. Update your OpenClaw config to use trusted-proxy auth.
  4. Restart the Gateway.
  5. Test WebSocket connections from the Control UI.
  6. Run openclaw security audit and check the results.

Need help getting your proxy configured? Check out our AI Setup Assistant for real-time guidance.

OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.