#Tailscale setup for local OpenClaw gateway

1 messages · Page 1 of 1 (latest)

blazing kernelBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

timber relicBOT
#
  1. Recommended architecture (safest / best-practice)
  • Keep the OpenClaw Gateway bound to loopback (127.0.0.1:18889) exactly as you have it now.
  • Put the host on Tailscale, then use Tailscale Serve to publish the Control UI + WebSocket over tailnet HTTPS without ever binding the Gateway to LAN/tailnet interfaces.
  • Auth best-practice: keep normal Gateway auth (token/password) available; optionally allow the Serve identity-header shortcut for Control UI/WS (gateway.auth.allowTailscale: true) only if you trust code running on the gateway host. (HTTP API endpoints still require token/password either way.)
    Docs: https://docs.openclaw.ai/gateway/tailscale and https://docs.openclaw.ai/gateway/remote
  1. Safest exact next command (only):
curl -fsSL https://tailscale.com/install.sh | sh

(Your spawn tailscale ENOENT means the tailscale CLI isn’t available, so OpenClaw can’t do Serve automation yet.)

  1. Should gateway bind stay loopback or be changed?
    Stay on loopback. For this goal, you do not want gateway.bind: "tailnet" or "lan"—Serve is specifically the “keep it on localhost, expose safely via Tailscale” path.
#

openclaw config get gateway.auth.allowTailscale returning “Config path not found …” just means the key is currently unset in your JSON5, not that the build doesn’t support it. (config get reads your config file; it doesn’t print defaults.)

In v2026.3.13 the config key is:

  • gateway.auth.allowTailscale (boolean)

Safest exact next step (only) to enable Tailscale identity-header auth for Control UI / WS:

openclaw config set gateway.auth.allowTailscale true

Notes (so you can choose the right security posture):

  • If you want the strictest setup, token/password-only is always valid: set gateway.auth.allowTailscale false and keep using the token/password in the UI.
  • With allowTailscale: true, Control UI/WS can be tokenless when reached via Tailscale Serve; HTTP API endpoints still require token/password.
rich moss
#

Hey! I run a similar setup — gateway on loopback + Tailscale. A few practical tips:

  1. Keep gateway bound to loopback (127.0.0.1). Don't change it. Tailscale Serve will handle the rest.

  2. The spawn tailscale ENOENT error just means Tailscale CLI isn't on the PATH that OpenClaw sees. Install Tailscale first: https://tailscale.com/download/linux — once tailscale status works in your terminal, OpenClaw will pick it up after a gateway restart.

  3. After Tailscale is installed and connected, the simplest safe approach:

tailscale serve --bg https+insecure://127.0.0.1:18889

This exposes your gateway over your tailnet via HTTPS with Tailscale's built-in certs. Only devices on your tailnet can reach it.

  1. You do NOT need to change bind to 0.0.0.0 — that would expose the gateway to your whole LAN which you don't want.

The key insight: Tailscale Serve acts as a reverse proxy on the same machine, so loopback binding is exactly right.

timber relicBOT
#

Hal’s overall instinct is right (loopback bind + Tailscale proxy is the safe shape), but I’d tweak two details to keep it “OpenClaw-best-practice” and avoid Tailscale CLI/version footguns:

  • Yes: keep the Gateway bound to loopback. Don’t switch to 0.0.0.0 just to make it reachable. That’s exactly what you don’t want.

  • spawn tailscale ENOENT: that’s not a “Tailscale is broken” signal, it’s simply “the tailscale binary wasn’t found in the environment OpenClaw is running under” (either not installed or not on the PATH seen by the systemd user service). If tailscale status works as the same user that runs the gateway, restarting the gateway usually resolves it.

  • On the tailscale serve … command: the Serve CLI has changed across Tailscale versions, and the https+insecure://… style isn’t what OpenClaw docs recommend. The cleanest approach is to let OpenClaw configure Serve for you:

    • set gateway.tailscale.mode: "serve" (or run openclaw gateway --tailscale serve)
    • keep gateway.bind: "loopback"
    • then access via https://<magicdns>/ (OpenClaw will also be able to use/verify identity headers when gateway.auth.allowTailscale: true)

OpenClaw docs for this exact flow:

If you want the strictest security posture even on tailnet: keep gateway.auth.allowTailscale: false and require token/password in the UI. Otherwise, allowTailscale: true is the convenience option (still tailnet-only, but assumes the gateway host itself is trusted).

vivid jewel
#

OpenClaw 2026.3.13 — status --all still reports missing scope: operator.read even though RPC + health are OK

Environment

  • OpenClaw: 2026.3.13
  • OS: Ubuntu 24
  • Gateway URL: ws://127.0.0.1:18889
  • Gateway bind: loopback
  • Service model: systemd user service

What is already proven locally

1) Gateway is actually running

systemctl --user status openclaw-gateway.service --no-pager shows:

  • Loaded: loaded (...; enabled; preset: enabled)
  • Active: active (running)

2) Gateway listener is live

status --all and gateway status --json both show port 18889 is busy with:

  • PID 80056
  • user openclaw2
  • command openclaw-gateway

3) Direct gateway RPC works

Command:
openclaw gateway status --json --url ws://127.0.0.1:18889 --token <known-good-token>

#

Result:

  • "rpc": { "ok": true, "url": "ws://127.0.0.1:18889" }

4) Built-in health is OK

Command:
openclaw health --json

Result:

  • "ok": true

5) Service-runtime check inside gateway status still misreports

Even with:

  • XDG_RUNTIME_DIR=/run/user/1003
  • DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1003/bus

openclaw gateway status --json ... still reports:

  • "runtime": { "status": "unknown" }
  • detail includes:
    • systemctl --user unavailable: Failed to connect to bus: Permission denied
    • Failed to read server status: Transport endpoint is not connected

But in the same general sudo/run context, this works:
systemctl --user status openclaw-gateway.service --no-pager

So local systemd/user-bus service truth is good; the mismatch seems to be in OpenClaw’s own service-runtime probe path.

Auth / scope work already done

6) Original root cause for gateway auth error was real

openclaw status --all reported:

  • Gateway unreachable (missing scope: operator.read)

7) We inspected device/token state directly

Using:
openclaw devices list --json --url ws://127.0.0.1:18889 --token <configured-token>

We found several openclaw-control-ui device entries that only had:

  • operator.admin
  • operator.approvals
  • operator.pairing

One CLI device entry had full scopes including:

  • operator.read
  • operator.write

8) We rotated one operator token explicitly with full scopes

Using:
openclaw devices rotate --json --url ws://127.0.0.1:18889 --token <configured-token> --device <device-id> --role operator --scope operator.read --scope operator.write --scope operator.admin --scope operator.approvals --scope operator.pairing

Rotation succeeded and returned a new token with:

  • operator.admin
  • operator.approvals
  • operator.pairing
  • operator.read
  • operator.write
#

9) We wrote that new token into the live config and verified readback

jq -r ".gateway.auth.token" /home/openclaw2/.openclaw/openclaw.json
now matches the newly rotated token.

10) The new token is valid when used explicitly

Using:
openclaw devices list --json --url ws://127.0.0.1:18889 --token <new-token>

The rotated token is accepted and the token record for that device shows:

  • operator.read
  • operator.write
  • plus prior operator scopes

The remaining mismatch

Despite all of the above, openclaw status --all still shows:

  • Gateway unreachable (missing scope: operator.read)
  • Gateway self unknown
  • Gateway service systemd installed · disabled · unknown

At the same time:

  • openclaw health --json is OK
  • direct openclaw gateway status --json --url ... --token <new-token> shows "rpc": { "ok": true }
  • systemctl --user status shows the service is active and enabled

Question

What is the most likely explanation for this remaining mismatch in 2026.3.13?

Specifically, is openclaw status --all likely using:

  1. a different cached/default token path than the one in openclaw.json
  2. a different device identity than the explicit token-based RPC commands
  3. a separate service-runtime probe path that is known to misreport under some sudo / user-bus contexts
  4. a known bug in status --all / doctor where status output can be stale or misleading even when health and direct RPC are OK

Most useful next ask

What exact command should we use to prove which auth source and probe path openclaw status --all is actually using, so we can separate:

  • real auth failure
  • service-runtime inspection bug
  • cached/default-token mismatch
timber relicBOT
#

That missing scope: operator.read warning is about your device-scoped operator token, not about “is the gateway up.” So it’s totally consistent to have:

  • RPC/health OK (especially when you pass the shared gateway --token …)
  • but status --all can’t call some “operator read” endpoints (like tools.catalog) using the cached device token it has for your CLI/UI device identity.

Safest fix is to rotate the operator device token and explicitly grant the scopes.

Next step (you’ll need the deviceId first):

openclaw devices list --json --url ws://127.0.0.1:18889 --token <known-good-token>

Then rotate with scopes (this is the actual fix; paste your deviceId into it):

openclaw devices rotate --device <deviceId> --role operator \
  --scope operator.read --scope operator.write \
#

Nice — the operator.read part is solved the “right” way (device token scopes), and your RPC/health proofs look solid.

For the remaining gateway status → runtime.status: "unknown" with:

  • systemctl --user unavailable: Failed to connect to bus: Permission denied
  • even though systemctl --user status openclaw-gateway.service works

…the most common gotcha on Ubuntu is: you’re running openclaw … from a sudo-influenced environment where SUDO_USER is set, and OpenClaw’s systemd probe path will try a systemctl --machine <user>@ --user … fallback. That can fail with “permission denied / transport endpoint not connected” even when plain systemctl --user … works.

Safest next check (no config changes): run the same command but explicitly without SUDO_USER in the environment (and ideally as the actual service user):

env -u SUDO_USER openclaw gateway status --json

If that makes runtime.status flip to running, you’ve confirmed it’s just the service-runtime probe heuristic being tripped by the sudo env, not an actual service problem.

#

If it still reports unknown, paste the exact runtime.detail blob from openclaw gateway status --json and whether you’re invoking OpenClaw via sudo, sudo -u openclaw2, or as a normal login shell user — the fix is usually just “run OpenClaw CLI as the same user that owns the systemd user service (no sudo wrapper), or strip sudo env vars for that call.”

If you can reproduce reliably, this is also worth filing as a bug (service probe should not pick the wrong systemd user scope just because SUDO_USER exists).

vivid jewel
#

OpenClaw 2026.3.13 — status --all reports missing operator.read despite healthy gateway and valid tokens

Environment state (verified)

Gateway:

  • Service active and running (systemctl --user status openclaw-gateway.service)
  • RPC reachable (openclaw gateway status --json)
  • rpc.ok = true
  • service.runtime.status = running

Overall health:

  • openclaw health --json → "ok": true

Identity state (verified)

Local CLI identity:

  • Located in ~/.openclaw/identity/
  • Client mode: cli
  • Token scopes include:
    • operator.admin
    • operator.approvals
    • operator.pairing
    • operator.read
    • operator.write

Paired devices:

  • Multiple openclaw-control-ui (webchat) devices exist in:
    ~/.openclaw/devices/paired.json

Their rotated tokens correctly include:

  • operator.admin
  • operator.approvals
  • operator.pairing
  • operator.read
  • operator.write

Critical mismatch discovered

Inside paired.json:

CLI device:

  • Top-level scopes include read/write → correct

Webchat devices:

  • Top-level scopes still show only:

["operator.admin","operator.approvals","operator.pairing"]

But nested token scopes show:

["operator.admin","operator.approvals","operator.pairing","operator.read","operator.write"]

So metadata appears inconsistent:

  • token scopes = correct
  • device top-level scopes = stale

Observed failure

openclaw status --all reports:

Gateway unreachable (missing scope: operator.read)

Gateway logs show real WS calls failing:

status
system-presence
config.get

#

Error:

missing scope: operator.read

Key technical question

Does openclaw status --all check:

  • paired device top-level scopes
    instead of
  • active token scopes?

Because if yes, stale paired metadata could explain the false failure.

What is the correct fix?

Safest resolution path:

1 — Remove and re-pair stale webchat devices
2 — Force metadata refresh of paired devices
3 — Rotate identity again
4 — Treat as cosmetic status bug if runtime is confirmed healthy

Looking for the intended corrective action.

vivid jewel
#

OpenClaw 2026.3.13 — verified why the non-loopback probe path did not work here

Thanks — I checked this locally step by step.

Proven on the installed CLI:

  • openclaw status --help does not support --url or --token
  • root openclaw --help also does not expose --url / --token
  • the correct command family for explicit remote probing here is:
    • openclaw gateway probe --url ... --token ...

Proven runtime/config state:

  • active config has:
    • gateway.port = 18889
    • gateway.mode = local
    • gateway.auth.mode = token
    • gateway.auth.allowTailscale = true
  • gateway.bind is not explicitly set in config
  • local code inspection shows unset gateway.bind defaults to loopback
  • live listener confirms:
    • 127.0.0.1:18889 only
    • not listening on tailnet IP

Proven probe result:

  • probing ws://100.x.x.x:18889 with insecure-private-WS allowed returns:
    • ECONNREFUSED
  • so the gateway is not reachable on raw non-loopback WS in the current service shape

Conclusion:

  • your idea was directionally useful, but on this install it maps to gateway probe, not status --all
  • and it still cannot work yet because the gateway is effectively loopback-bound

Question:

  • for 2026.3.13, what is the safest minimal way to test whether the remaining status --all / operator.read issue is just localhost identity/probe behavior versus stale paired-device metadata, without widening the live gateway bind?
  • preferred options to compare:
    1. temporary foreground gateway with --bind tailnet
    2. config/service change to gateway.bind=tailnet
    3. another read-only diagnostic path that preserves loopback bind
timber relicBOT
#

You don’t need to widen the bind at all here. The clean “A/B test” is: make status --all probe the gateway through your existing Tailscale Serve HTTPS endpoint (wss://) instead of loopback ws://, for one command, and see if the operator.read error disappears.

Why this is the safest discriminator:

  • In 2026.3.13 the status command’s gateway probe path uses a lightweight “probe” client.
  • That probe behaves differently depending on whether the target host is loopback vs non-loopback (notably around device identity / scope handling).
  • Using Tailscale Serve changes only the probe transport, not your gateway listener exposure (gateway stays 127.0.0.1).

Safest minimal test (single command, no bind change)

Run:

OPENCLAW_GATEWAY_URL="wss://arthur-a6.tail3c61b.ts.net" openclaw status --all

Interpretation:

  • If the missing scope: operator.read line goes away (or the gateway becomes “reachable” in status), the problem is status’s localhost probe behavior (not real gateway auth, not device metadata).
  • If it does not change, then you’re likely dealing with stale/incorrect paired-device scope metadata being used by the client (or another status/daemon probe bug), and we can focus there next.
#

Notes:

  • status doesn’t take --url/--token, but it does honor OPENCLAW_GATEWAY_URL (you can see this in the “Gateway target / Source” verbose details).
  • This keeps the gateway loopback-bound; you’re just asking the CLI to reach it via your Serve proxy.

If you paste the delta between:

  • normal openclaw status --all
  • and the one-liner above
    …I can tell you which of your numbered hypotheses (1–4) it conclusively confirms.
vivid jewel
#

OpenClaw 2026.3.13 — verified OPENCLAW_GATEWAY_URL override, but A/B test is still blocked before operator.read

Thanks — I checked this locally.

Proven now:

  • openclaw status --all does honor OPENCLAW_GATEWAY_URL
  • the override shows up in status --all as:
    • Gateway target: ...
    • Source: env OPENCLAW_GATEWAY_URL

Current setup/proof:

  • gateway is healthy locally
  • live listener is loopback-only (127.0.0.1:18889)
  • Tailscale Serve is active as HTTP-only proxy to the local gateway port
  • no HTTPS/WSS Serve endpoint is available on this account
  • MagicDNS is disabled tailnet-wide, so the .ts.net hostname does not resolve locally from shell

A/B test results:

  1. OPENCLAW_GATEWAY_URL="http://<tsnet-hostname>" with insecure-private-WS allowed:
    • status --all uses the env override
    • fails with:
      • getaddrinfo ENOTFOUND <tsnet-hostname>
  2. OPENCLAW_GATEWAY_URL="http://<tailnet-ip>" with insecure-private-WS allowed:
    • status --all uses the env override
    • fails with:
      • Unexpected server response: 404

Interpretation:

  • we successfully moved status --all off local loopback target selection
  • but the test still does not reach the operator.read layer
  • hostname form dies at DNS
  • tailnet-IP form reaches something remote enough to stop being ENOTFOUND, but the WS/HTTP path comes back 404

Question:

  • for 2026.3.13, what exact URL form/path should OPENCLAW_GATEWAY_URL use when targeting a Tailscale Serve HTTP endpoint?
  • is status --all compatible with HTTP-only Serve at all, or does this path require true wss:// / HTTPS-backed Serve?
  • if a path suffix is required for the gateway WS probe, what is the exact expected path?
timber relicBOT
#

openclaw status --all is doing a WebSocket probe. So OPENCLAW_GATEWAY_URL needs to point at the Gateway WS endpoint (ws/wss), not an HTTP URL.

1) Correct URL scheme + path

  • Use ws://… (or wss://…), not http://….
  • No path suffix is expected for the Gateway WS probe (it’s just the WS server on that origin, i.e. /).

So the shape should be:

  • HTTP-only Serve (plaintext on tailnet):
    OPENCLAW_GATEWAY_URL="ws://<tailnet-ip>[:<serve-port>]"
    (and you already know you need OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1 for non-loopback plaintext)

  • HTTPS Serve (preferred):
    OPENCLAW_GATEWAY_URL="wss://<magicdns-or-tsnet-hostname>"

2) Is status --all compatible with HTTP-only Serve?

Yes only if your Serve endpoint actually forwards WebSocket upgrades to the gateway (it usually does when it’s proxying an HTTP backend). If you truly have no HTTPS/WSS available, the plaintext ws://<tailnet-ip> path is the only way to “move off loopback” without changing gateway.bind, but it’s inherently a break-glass mode (still tailnet-only, but no TLS).

#

3) Why you’re seeing Unexpected server response: 404

That 404 is almost certainly because the WS upgrade request is hitting the wrong Tailscale Serve listener (wrong port / wrong Serve target), not because OpenClaw needs a special WS path.

The external port you must use is whatever Tailscale Serve is listening on, which is often 80 (HTTP) / 443 (HTTPS) — not your backend port 18889.

To answer “what exact host:port should I use” precisely, I need one thing:

Paste the output of:

  • tailscale serve status

Specifically the lines that show what it’s serving on (ports / any path mounts) and what it forwards to. With that, I can tell you the exact OPENCLAW_GATEWAY_URL=ws://… form that should work in your current “HTTP-only Serve + loopback-bound gateway” setup.

vivid jewel
#

OpenClaw 2026.3.13 — status --all gateway override credential behavior

Testing gateway status behavior when using OPENCLAW_GATEWAY_URL.

What is proven locally:

  • Gateway service is running normally.
  • status --all works normally on loopback.
  • When using OPENCLAW_GATEWAY_URL to force non-loopback probe:
    • Gateway becomes reachable.
    • But status fails with:
      gateway url override requires explicit credentials

Observed behavior:

  • Error suggests:
    Fix: pass --token or --password (or gatewayToken in tools).

Question:

For 2026.3.13, what is the correct minimal way to satisfy the credential requirement when using OPENCLAW_GATEWAY_URL with status --all?

Specifically:

  • Should existing gateway token in config be enough?
  • Or is an additional explicit credential required for override mode?
  • If explicit is required, what is the intended method for status (since it has no --token flag)?
timber relicBOT
#

That behavior is intentional in 2026.3.13 and matches the “URL override safety” rules:

  • When you override the gateway URL via OPENCLAW_GATEWAY_URL, OpenClaw treats that as a potentially remote target and will not implicitly reuse the token/password from openclaw.json.
  • For an env URL override, it will only proceed if it can also find credentials in env (OPENCLAW_GATEWAY_TOKEN / OPENCLAW_GATEWAY_PASSWORD). Otherwise you get:
    gateway url override requires explicit credentials

Since status has no --token, the intended minimal way is:

Minimal one-shot invocation

Set both env vars for the same command:

OPENCLAW_GATEWAY_URL="ws://<tailnet-ip>:<serve-port>" \
OPENCLAW_GATEWAY_TOKEN="$(jq -r '.gateway.auth.token' ~/.openclaw/openclaw.json)" \
openclaw status --all
#

(And because that’s non-loopback plaintext ws://, you’ll also need OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1 in that same invocation unless you’re using wss://.)

Answering your bullets

  • Should existing gateway token in config be enough? Not when you use OPENCLAW_GATEWAY_URL override. (By design.)
  • Is an additional explicit credential required? Yes — explicit in the environment (OPENCLAW_GATEWAY_TOKEN/PASSWORD) for env override.
  • Intended method for status with no --token? Provide the credential via env vars, or use openclaw gateway probe --url … --token … instead of status.

This is the same policy described in the Remote Access docs (URL override safety): https://docs.openclaw.ai/gateway/remote

vivid jewel
#

OpenClaw 2026.3.13 — decisive A/B result on status --all loopback vs non-loopback probe

Local proof is now decisive.

A/B result:

  • Default openclaw status --all on local loopback:
    • gateway target = local loopback
    • fails with:
      • missing scope: operator.read
  • openclaw status --all with non-loopback gateway URL override plus explicit env credential:
    • gateway reachable
    • no channel issues
    • no operator.read failure

So this is now proven not to be:

  • real gateway down
  • broken service/runtime
  • bad main gateway token in config
  • general auth failure

Strongest conclusion:

  • in 2026.3.13, the default loopback status --all probe path is producing a false negative / auth-scope mismatch
  • the same runtime works when forced through non-loopback override with explicit credentials

Question:

  • Is this a known localhost probe bug/regression in status --all for 2026.3.13?
  • Safest recommendation from here:
    1. ignore as status-only false negative
    2. use a known workaround
    3. upgrade/downgrade target version