* fix(gateway): bind auth limits to ingress attribution * fix(gateway): close remaining ingress auth gaps * fix(gateway): carry attribution into new ingress paths * fix(gateway): close ingress ownership gaps * fix(gateway): complete proxy ingress hardening * fix(gateway): stabilize managed Tailscale ingress * fix(gateway): make Tailscale cleanup ownership-safe Refuse reset-on-exit publication until Tailscale exposes an atomic owner-bound cleanup operation, and migrate legacy configs with Doctor. * fix(gateway): finish ingress ownership repair * fix(gateway): own managed Tailscale route lifetime Run managed Serve and Funnel routes as foreground claims tied to the Gateway lifecycle. Retire named Service config through Doctor because Tailscale Services cannot run in foreground mode. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): align Tailscale consumers and build guards Remove the retired named-service config from Telegram Mini App URL resolution and register the lifecycle worker as an explicit production entry. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): preserve retired Tailscale inputs Keep Funnel enabled when removing an ignored named-Service setting and accept the legacy positive reset flag as a no-op now that managed routes always follow Gateway lifetime. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): preserve Tailscale route diagnostics Prefer the actionable foreground CLI failure captured during timeout cleanup, and cover the original delayed-failure ordering. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): reconcile Tailscale ingress with main Preserve current ingress ownership contracts after the rebase, retire the obsolete device-auth migration check, validate route-owner IPC, and move Tailscale auth coverage onto the managed listener. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * fix(gateway): finish ingress rebase coverage Unify the rebased net imports and let module-reset WebSocket tests prepare attribution through the same fresh module instance as the handler. Co-authored-by: Pavan Kumar Gondhi <pavangondhi@gmail.com> * test(gateway): align run-loop server fixture --------- Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
9.4 KiB
summary, read_when, title
| summary | read_when | title | ||
|---|---|---|---|---|
| Integrated Tailscale Serve/Funnel for the Gateway dashboard |
|
Tailscale |
OpenClaw can auto-configure Tailscale Serve (tailnet) or Funnel (public) for the Gateway dashboard and WebSocket port. This keeps the gateway bound to loopback while Tailscale provides HTTPS, routing, and (for Serve) identity headers.
Looking for the step-by-step setup? See [Give your Gateway a stable HTTPS URL](/gateway/stable-https-url).Modes
gateway.tailscale.mode:
| Mode | Behavior |
|---|---|
serve |
Tailnet-only Serve via tailscale serve. The gateway stays on 127.0.0.1. |
funnel |
Public HTTPS via tailscale funnel. Requires a shared password. |
off (default) |
No Tailscale automation. |
Status and audit output use Tailscale exposure for this OpenClaw Serve/Funnel mode. off means OpenClaw is not managing Serve or Funnel; it does not mean the local Tailscale daemon is stopped or logged out.
Config examples
Tailnet-only (Serve)
{
gateway: {
bind: "loopback",
tailscale: { mode: "serve" },
},
}
Open: https://<magicdns>/ (or your configured gateway.controlUi.basePath)
Tailnet-only (bind to Tailnet IP)
Use this to have the gateway listen directly on the Tailnet IP, with no Serve/Funnel:
{
gateway: {
bind: "tailnet",
auth: { mode: "token", token: "your-token" },
},
}
Connect a native or CLI client from another Tailnet device:
- WebSocket:
ws://<tailscale-ip>:18789
Do not use the direct plain-HTTP address for the browser Control UI. Remote plain HTTP cannot create browser device identity, and token/password auth does not replace it. Use Tailscale Serve for the Control UI.
When a bindable Tailnet IPv4 is present, the Gateway also requires `http://127.0.0.1:18789` for authenticated same-host clients. If no Tailnet address is available at startup, it falls back to loopback only; restart after Tailscale becomes available to add direct Tailnet access. Neither path adds LAN or public exposure.Public internet (Funnel + shared password)
{
gateway: {
bind: "loopback",
tailscale: { mode: "funnel" },
auth: { mode: "password", password: "replace-me" },
},
}
Prefer OPENCLAW_GATEWAY_PASSWORD over committing a password to disk.
CLI examples
openclaw gateway --tailscale serve
openclaw gateway --tailscale funnel --auth password
Auth
gateway.auth.mode controls the handshake:
| Mode | Use case |
|---|---|
none |
Private ingress only |
token (default when OPENCLAW_GATEWAY_TOKEN is set) |
Shared token |
password |
Shared secret via OPENCLAW_GATEWAY_PASSWORD or config |
trusted-proxy |
Identity-aware reverse proxy; see Trusted Proxy Auth |
Tailscale identity headers (Serve only)
When tailscale.mode: "serve" and gateway.auth.allowTailscale is true, Control UI/WebSocket auth can use Tailscale identity headers (tailscale-user-login) instead of a token/password. OpenClaw verifies the header by resolving the request's x-forwarded-for address via the local Tailscale daemon (tailscale whois) and matching it to the header login before accepting it. A request only qualifies when it reaches OpenClaw's dedicated managed-Tailscale listener with Tailscale's x-forwarded-for, x-forwarded-proto, and x-forwarded-host headers; sending those headers to the ordinary Gateway listener is rejected.
This tokenless flow assumes the gateway host is trusted. If untrusted local code may run on the same host, set gateway.auth.allowTailscale: false and require token/password auth instead.
Scope of the bypass:
- Applies to the Control UI WebSocket auth surface and read-only
GET/HEADrequests for Control UI profile avatars. Other HTTP API endpoints (/v1/*,/tools/invoke,/api/channels/*, etc.) never use Tailscale identity-header auth; they always follow the gateway's normal HTTP auth mode. - For Control UI operator sessions that already carry browser device identity, a verified Tailscale identity skips the bootstrap-token/QR pairing round trip.
- It does not bypass device identity itself: device-less clients are still rejected, and node-role connections still go through normal pairing and auth checks.
Notes
- Tailscale Serve/Funnel requires the
tailscaleCLI installed and logged in. tailscale.mode: "funnel"refuses to start unless auth mode ispassword, to avoid public exposure.- OpenClaw holds Serve/Funnel as a foreground Tailscale claim. Gateway startup succeeds only after the claim is active, and stopping or losing the Gateway releases it automatically.
- Named Tailscale Services are not supported by managed ingress because Tailscale requires them to run as persistent background routes. Existing
gateway.tailscale.serviceNameinstalls must runopenclaw doctor --fix; Doctor disables managed ingress and removes the key. Inspect the retained Service route, clear it withtailscale serve clear <service-name>, then enable device Serve withgateway.tailscale.mode: "serve"if desired. - Older releases could advertise an externally configured default HTTPS Serve route that targeted a
gateway.bind: "lan"listener. That route no longer has trusted ingress provenance. Runopenclaw doctorto preview an atomic migration togateway.bind: "loopback"plusgateway.tailscale.mode: "serve"; apply it withopenclaw doctor --fix, then restart the Gateway so it can claim the route through managed ingress. Doctor does not reset Tailscale state or guess how to rewrite custom Serve ports and Tailscale Services; migrate those manually. gateway.tailscale.preserveFunnel: trueis a deprecated migration guard. It detects an externally configuredtailscale funnelroute before reapplying Serve. If that route still targets the ordinary Gateway listener, OpenClaw leaves it unchanged and warns because requests lack managed-ingress provenance. Plugin-authenticated webhook routes such as Google Chat and SMS keep using their own signature/auth checks and ignore forwarded client claims; Gateway-authenticated HTTP and WebSocket routes reject that ingress. First configure a durablegateway.auth.password(prefer a SecretRef) orOPENCLAW_GATEWAY_PASSWORD, then setgateway.auth.modetopassword. After password auth is ready, runopenclaw config set gateway.tailscale.mode funnel, thenopenclaw config unset gateway.tailscale.preserveFunnel; managed Funnel targets the dedicated ingress.gateway.bind: "tailnet"uses a direct Tailnet bind (no HTTPS, no Serve/Funnel) plus required local127.0.0.1when a Tailnet IPv4 is available; otherwise it falls back to loopback only.gateway.bind: "auto"prefers loopback; usetailnetto limit network exposure to the Tailnet while retaining same-host loopback access.- Serve/Funnel only expose the Gateway control UI + WS. Nodes connect over the same Gateway WS endpoint, so Serve works for node access too.
Tailscale prerequisites and limits
- Serve requires HTTPS enabled for your tailnet; the CLI prompts if it is missing.
- Serve injects Tailscale identity headers; Funnel does not.
- OpenClaw-managed Serve/Funnel proxy to a dedicated
127.0.0.1:<ephemeral-port>listener while ordinary local clients keep the configured Gateway port. Startup fails closed rather than sharing listener provenance, and the foreground claim releases the route when its Gateway owner disappears. - Funnel requires Tailscale v1.38.3+, MagicDNS, HTTPS enabled, and a funnel node attribute.
- Funnel only supports ports
443,8443, and10000over TLS. - Funnel on macOS requires the open-source Tailscale app variant.
Browser control (remote Gateway + local browser)
To run the Gateway on one machine but drive a browser on another, run a node host on the browser machine and keep both on the same tailnet. The Gateway proxies browser actions to the node; no separate control server or Serve URL is needed.
Avoid Funnel for browser control; treat node pairing like operator access.
Learn more
- Tailscale Serve overview: https://tailscale.com/kb/1312/serve
tailscale servecommand: https://tailscale.com/kb/1242/tailscale-serve- Tailscale Funnel overview: https://tailscale.com/kb/1223/tailscale-funnel
tailscale funnelcommand: https://tailscale.com/kb/1311/tailscale-funnel