Tailscale Serve and Funnel for the Gateway Dashboard
Learn how OpenClaw automatically configures Tailscale Serve or Funnel to expose your Gateway dashboard securely over HTTPS, keeping it bound to loopback. Ideal for administrators needing remote or public access.
Read this when
- Exposing the Gateway Control UI outside localhost
- Automating tailnet or public dashboard access
OpenClaw is able to automatically set up Tailscale Serve (tailnet-only) or Funnel (public) for the Gateway dashboard and its WebSocket port. With this, the gateway stays bound to loopback while Tailscale handles HTTPS, routing, and, for Serve, identity headers.
Note
Need the full walkthrough? Check out Give your Gateway a stable HTTPS URL.
Modes
gateway.tailscale.mode:
| Mode | Behavior |
|---|---|
serve | Tailnet-only Serve via tailscale serve. The gateway remains on 127.0.0.1. |
funnel | Public HTTPS through tailscale funnel. A shared password is mandatory. |
off (default) | No Tailscale automation is performed. |
Status and audit output report Tailscale exposure for this OpenClaw Serve/Funnel mode. off signals that OpenClaw is not handling Serve or Funnel; it does not imply the local Tailscale daemon has stopped or logged out.
Config examples
Tailnet-only (Serve)
{
gateway: {
bind: "loopback",
tailscale: { mode: "serve" },
},
}
Open: https://<magicdns>/ (or the gateway.controlUi.basePath you set)
Tailnet-only (bind to Tailnet IP)
Use this to make the gateway listen directly on the Tailnet IP, bypassing Serve/Funnel:
{
gateway: {
bind: "tailnet",
auth: { mode: "token", token: "your-token" },
},
}
From another Tailnet device, connect a native or CLI client:
- WebSocket:
ws://<tailscale-ip>:18789
For the browser Control UI, avoid the direct plain-HTTP address. Remote plain HTTP cannot generate browser device identity, and token/password auth is not a substitute. The Control UI should be accessed through Tailscale Serve.
Note
When a bindable Tailnet IPv4 exists, the Gateway also needs
http://127.0.0.1:18789for authenticated clients on the same host. If no Tailnet address is available at startup, it reverts to loopback only; restart once Tailscale is up to enable direct Tailnet access. Neither option adds LAN or public exposure.
Public internet (Funnel + shared password)
{
gateway: {
bind: "loopback",
tailscale: { mode: "funnel" },
auth: { mode: "password", password: "replace-me" },
},
}
Choose OPENCLAW_GATEWAY_PASSWORD instead of writing a password to disk.
The Funnel URL also works for devices inside the tailnet. Tailscale tags public requests as Funnel traffic but routes tailnet peers through its Serve identity path; OpenClaw recognizes both on its dedicated listener and still enforces the configured Funnel password.
CLI examples
openclaw gateway --tailscale serve
openclaw gateway --tailscale funnel --auth password
Auth
gateway.auth.mode governs 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 rely on Tailscale identity headers (tailscale-user-login) instead of a token/password. OpenClaw validates the header by resolving the request's x-forwarded-for address through the local Tailscale daemon (tailscale whois) and confirming it matches the header login before acceptance. A request qualifies only if it arrives at OpenClaw's dedicated managed-Tailscale listener with Tailscale's x-forwarded-for, x-forwarded-proto, and x-forwarded-host headers. Those headers never grant managed Serve provenance or tokenless auth on the regular Gateway listener.
This tokenless flow assumes the gateway host is trusted. If untrusted local code might run on the same host, set gateway.auth.allowTailscale: false and enforce token/password auth instead.
Scope of the bypass:
- It covers 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 standard HTTP auth mode. - For Control UI operator sessions already carrying 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.
Externally managed Serve and Funnel
You can direct a native Tailscale Serve or Funnel route at the ordinary Gateway listener when another service owns the route. Set the route's immediate source narrowly in gateway.trustedProxies, and make sure it overwrites or safely rebuilds X-Forwarded-For. OpenClaw then treats the request as generic trusted-proxy ingress, uses the forwarded client address for rate limits, and applies the configured gateway auth mode normally. Because Funnel is public, Gateway-protected routes reject externally managed Funnel ingress when gateway.auth.mode is none; configure token, password, or trusted-proxy authentication. The aggregate health, readiness, and startup probes retain their existing unauthenticated responses, without exposing detailed readiness or startup data. See Health and readiness.
This compatibility path does not provide managed Tailscale semantics: gateway.auth.allowTailscale cannot offer tokenless auth, OpenClaw never invokes tailscale whois, and it neither owns nor clears the external route. Without an explicitly trusted source and a valid non-loopback forwarded client address, Gateway-authenticated routes fail with proxy_attribution_required. When the proxy connects over loopback, adding 127.0.0.1 to trustedProxies explicitly trusts same-host processes to supply proxy attribution; keep token or password auth enabled unless every process on the host shares the same trust boundary.
Notes
- Tailscale Serve/Funnel needs the
tailscaleCLI installed and logged in. tailscale.mode: "funnel"will not start unless auth mode ispassword, to prevent public exposure.- OpenClaw treats 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.
- When upgrading from an older OpenClaw release, a persistent HTTPS root route may still own port 443. Because that route is indistinguishable from an operator-managed route, OpenClaw does not change it: startup explains the conflict and exits with status 78 instead of entering a systemd restart loop. Inspect
tailscale serve status. If you confirm it is the stale route created by older managed Serve/Funnel, remove only its root handler withtailscale serve --yes --https=443 --set-path=/ offortailscale funnel --yes --https=443 --set-path=/ off, then restart the Gateway. If the route is intentionally external, disable managed Tailscale ingress and use thetrustedProxiespath above. - 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 does not automatically gain trusted ingress provenance. Prefer migrating to managed ingress: runopenclaw doctorto preview an atomic change togateway.bind: "loopback"plusgateway.tailscale.mode: "serve"; apply it withopenclaw doctor --fix, then restart the Gateway. Doctor does not reset Tailscale state or guess how to rewrite custom Serve ports and Tailscale Services. If another service must retain ownership, use the explicittrustedProxiescompatibility path above. 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 the route is not managed ingress. Gateway-authenticated routes work only through the explicittrustedProxiescompatibility path above and continue to require the configured auth; plugin-authenticated webhook routes such as Google Chat and SMS keep using their own signature/auth checks. To migrate, first configure a durablegateway.auth.password(prefer a SecretRef) orOPENCLAW_GATEWAY_PASSWORD, setgateway.auth.modetopassword, runopenclaw config set gateway.tailscale.mode funnel, thenopenclaw config unset gateway.tailscale.preserveFunnel.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.
- Tailnet Serve traffic injects Tailscale identity headers. Public Funnel traffic uses a Funnel marker instead, while tailnet access to the same Funnel URL follows the Serve identity path.
- 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