Skip to main content

Gateway: ports, “already running”, and remote mode

gateway.port controls the single multiplexed port for WebSocket + HTTP (Control UI, hooks, etc.). Precedence:
“Running” is the supervisor’s view (launchd/systemd/schtasks); the connectivity probe is the CLI actually connecting to the gateway WebSocket. Trust these lines from openclaw gateway status: Probe target: (the URL the probe used), Listening: (what is actually bound on the port), Last gateway error: (common root cause when the process is alive but the port is not listening).
You are editing one config file while the service runs another (often a --profile / OPENCLAW_STATE_DIR mismatch).Fix, run from the same --profile / environment you want the service to use:
OpenClaw enforces a runtime lock by binding the WebSocket listener immediately on startup (default ws://127.0.0.1:18789). If the bind fails with EADDRINUSE, it throws GatewayLockError (“another gateway instance is already listening”).Fix: stop the other instance, free the port, or run with openclaw gateway --port <port>.
Set gateway.mode: "remote" and point to a remote WebSocket URL, optionally with shared-secret remote credentials:
  • openclaw gateway only starts when gateway.mode is local (or you pass an override flag).
  • The macOS app watches the config file and switches modes live when these values change.
  • gateway.remote.token / .password are client-side remote credentials only; they do not enable local gateway auth by themselves.
Your gateway auth path and the UI’s auth method do not match.Facts (from code):
  • The Control UI keeps the token in sessionStorage, scoped to the current browser tab and selected gateway URL, so same-tab refreshes keep working without long-lived localStorage token persistence.
  • On AUTH_TOKEN_MISMATCH, trusted clients can attempt one bounded retry with a cached device token when the gateway returns retry hints (canRetryWithDeviceToken=true, recommendedNextStep=retry_with_device_token).
  • That cached-token retry reuses the cached approved scopes stored with the device token; explicit deviceToken / explicit scopes callers keep their requested scope set instead of inheriting cached scopes.
  • Outside that retry path, connect auth precedence is explicit shared token/password first, then explicit deviceToken, then stored device token, then bootstrap token.
  • Built-in setup-code bootstrap returns a node device token with scopes: [] plus a bounded operator handoff token for trusted mobile onboarding. The operator handoff can read setup-time native configuration but does not grant pairing mutation scopes or operator.admin.
Fix:
  • Fastest: openclaw dashboard (prints + copies the dashboard URL, tries to open; shows an SSH hint if headless).
  • No token yet: openclaw doctor --generate-gateway-token.
  • Remote: tunnel first with ssh -N -L 18789:127.0.0.1:18789 user@host, then open http://127.0.0.1:18789/.
  • Shared-secret mode: set gateway.auth.token / OPENCLAW_GATEWAY_TOKEN or gateway.auth.password / OPENCLAW_GATEWAY_PASSWORD, then paste the matching secret in Control UI settings.
  • Tailscale Serve mode: confirm gateway.auth.allowTailscale is enabled and you are opening the Serve URL, not a raw loopback/tailnet URL that bypasses Tailscale identity headers.
  • Trusted-proxy mode: confirm you are coming through the configured identity-aware proxy. Same-host loopback proxies also need gateway.auth.trustedProxy.allowLoopback = true.
  • Mismatch persists after the one retry: rotate/re-approve the paired device token:
  • Rotate denied: paired-device sessions can rotate only their own device unless they also have operator.admin, and explicit --scope values cannot exceed the caller’s current operator scopes.
  • Still stuck: openclaw status --all plus Troubleshooting. See Dashboard for auth details.
tailnet bind picks a Tailscale IP from your network interfaces (100.64.0.0/10). If the machine is not on Tailscale (or the interface is down), the Gateway falls back to loopback instead of exposing another network interface.Fix: start Tailscale on that host and restart the Gateway, or switch explicitly to gateway.bind: "loopback" / "lan".tailnet is explicit; auto prefers loopback. Use gateway.bind: "tailnet" to limit non-loopback exposure to the Tailnet while retaining the required same-host 127.0.0.1 listener.
Usually no - one Gateway can run multiple messaging channels and agents. Use multiple Gateways only for redundancy (for example a rescue bot) or hard isolation, and isolate each with its own OPENCLAW_CONFIG_PATH, OPENCLAW_STATE_DIR, agents.defaults.workspace, and unique gateway.port.Recommended: openclaw --profile <name> ... per instance (auto-creates ~/.openclaw-<name>), a unique gateway.port per profile config (or --port for manual runs), and a per-profile service with openclaw --profile <name> gateway install.Profiles also suffix service names: launchd ai.openclaw.<profile>, systemd openclaw-gateway-<profile>.service, Windows OpenClaw Gateway (<profile>). The unqualified openclaw-gateway systemd unit only exists for the default profile; the legacy pre-rename systemd unit name clawdbot-gateway is migrated automatically.Full guide: Multiple gateways.
The Gateway is a WebSocket server and expects the first message to be a connect frame. Anything else closes the connection with code 1008 (policy violation).Common causes: you opened the HTTP URL in a browser instead of a WS client, used the wrong port/path, or a proxy/tunnel stripped auth headers or sent a non-Gateway request.Fix: use the WS URL (ws://<host>:18789, or wss://... over HTTPS), do not open the WS port in a normal browser tab, and include the token/password in the connect frame when auth is on. CLI/TUI example:
Protocol details: Gateway protocol.