docs/gateway/tailscale.md
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.
serve: Tailnet-only Serve via tailscale serve. The gateway stays on 127.0.0.1.funnel: Public HTTPS via tailscale funnel. OpenClaw 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.
Set gateway.auth.mode to control the handshake:
none (private ingress only)token (default when OPENCLAW_GATEWAY_TOKEN is set)password (shared secret via OPENCLAW_GATEWAY_PASSWORD or config)trusted-proxy (identity-aware reverse proxy; see Trusted Proxy Auth)When tailscale.mode = "serve" and gateway.auth.allowTailscale is true,
Control UI/WebSocket auth can use Tailscale identity headers
(tailscale-user-login) without supplying a token/password. OpenClaw verifies
the identity by resolving the x-forwarded-for address via the local Tailscale
daemon (tailscale whois) and matching it to the header before accepting it.
OpenClaw only treats a request as Serve when it arrives from loopback with
Tailscale’s x-forwarded-for, x-forwarded-proto, and x-forwarded-host
headers.
For Control UI operator sessions that include browser device identity, this
verified Serve path also skips the device-pairing round trip. It does not bypass
browser device identity: device-less clients are still rejected, and node-role
or non-Control UI WebSocket connections still follow the normal pairing and
auth checks.
HTTP API endpoints (for example /v1/*, /tools/invoke, and /api/channels/*)
do not use Tailscale identity-header auth. They still follow the gateway's
normal HTTP auth mode: shared-secret auth by default, or an intentionally
configured trusted-proxy / private-ingress none setup.
This tokenless flow assumes the gateway host is trusted. If untrusted local code
may run on the same host, disable gateway.auth.allowTailscale and require
token/password auth instead.
To require explicit shared-secret credentials, set gateway.auth.allowTailscale: false
and use gateway.auth.mode: "token" or "password".
{
gateway: {
bind: "loopback",
tailscale: { mode: "serve" },
},
}
Open: https://<magicdns>/ (or your configured gateway.controlUi.basePath)
Use this when you want the Gateway to listen directly on the Tailnet IP (no Serve/Funnel).
{
gateway: {
bind: "tailnet",
auth: { mode: "token", token: "your-token" },
},
}
Connect from another Tailnet device:
http://<tailscale-ip>:18789/ws://<tailscale-ip>:18789{
gateway: {
bind: "loopback",
tailscale: { mode: "funnel" },
auth: { mode: "password", password: "replace-me" },
},
}
Prefer OPENCLAW_GATEWAY_PASSWORD over committing a password to disk.
openclaw gateway --tailscale serve
openclaw gateway --tailscale funnel --auth password
tailscale CLI to be installed and logged in.tailscale.mode: "funnel" refuses to start unless auth mode is password to avoid public exposure.gateway.tailscale.resetOnExit if you want OpenClaw to undo tailscale serve
or tailscale funnel configuration on shutdown.gateway.bind: "tailnet" is a direct Tailnet bind (no HTTPS, no Serve/Funnel).gateway.bind: "auto" prefers loopback; use tailnet if you want Tailnet-only.If you run the Gateway on one machine but want to drive a browser on another machine, run a node host on the browser machine and keep both on the same tailnet. The Gateway will proxy browser actions to the node; no separate control server or Serve URL needed.
Avoid Funnel for browser control; treat node pairing like operator access.
443, 8443, and 10000 over TLS.tailscale serve command: https://tailscale.com/kb/1242/tailscale-servetailscale funnel command: https://tailscale.com/kb/1311/tailscale-funnel