> ## Documentation Index
> Fetch the complete documentation index at: https://docs2.openclaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Gateway ports, already running, and remote mode

## Gateway: ports, "already running", and remote mode

> **What port does the Gateway use?**
>
> `gateway.port` controls the single multiplexed port for WebSocket + HTTP (Control UI, hooks, etc.). Precedence:
>
> ```text theme={"theme":{"light":"min-light","dark":"min-dark"}}
> --port > OPENCLAW_GATEWAY_PORT > gateway.port > default 18789
> ```

> **Why does openclaw gateway status say "Runtime: running" but "Connectivity probe: failed"?**
>
> "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).

> **Why does openclaw gateway status show "Config (cli)" and "Config (service)" different?**
>
> 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:
>
> ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
> openclaw gateway install --force
> ```

> **What does "another gateway instance is already listening" mean?**
>
> 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>`.

> **How do I run OpenClaw in remote mode (client connects to a Gateway elsewhere)?**
>
> Set `gateway.mode: "remote"` and point to a remote WebSocket URL, optionally with shared-secret remote credentials:
>
> ```json5 theme={"theme":{"light":"min-light","dark":"min-dark"}}
> {
>   gateway: {
>     mode: "remote",
>     remote: {
>       url: "ws://gateway.tailnet:18789",
>       token: "your-token",
>       password: "your-password",
>     },
>   },
> }
> ```
>
> * `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.

> **The Control UI says "unauthorized" (or keeps reconnecting). What now?**
>
> 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:
>   ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
>   openclaw devices list
>   openclaw devices rotate --device <id> --role operator
>   ```
> * 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](https://docs2.openclaw.ai/gateway/troubleshooting.md). See [Dashboard](https://docs2.openclaw.ai/web/dashboard.md) for auth details.

> **I set gateway.bind tailnet but it listens only on loopback**
>
> `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.

> **Can I run multiple Gateways on the same host?**
>
> 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](https://docs2.openclaw.ai/gateway/multiple-gateways.md).

> **What does "invalid handshake" / code 1008 mean?**
>
> 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:
>
> ```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
> openclaw tui --url ws://<host>:18789 --token <token>
> ```
>
> Protocol details: [Gateway protocol](https://docs2.openclaw.ai/gateway/protocol.md).
