- Operators (you, or the macOS app): direct LAN/Tailnet WebSocket is simplest when the Gateway is reachable; SSH tunneling is the universal fallback.
- Nodes (iOS/Android and other devices): connect to the Gateway WebSocket (LAN/tailnet or SSH tunnel).
The core idea
The Gateway WebSocket binds to loopback by default, on port18789 (gateway.port). For remote use, either expose it through Tailscale Serve / a trusted LAN-Tailnet bind, or forward the loopback port over SSH.
Topology options
| Setup | Where the Gateway runs | Best for |
|---|---|---|
| Always-on Gateway in your tailnet | Persistent host (VPS or home server), reached via Tailscale or SSH | Laptops that sleep often but need the agent always-on. See exe.dev (easy VM) or Hetzner (production VPS). |
| Home desktop | Desktop; laptop connects remotely via the macOS app’s remote mode (Settings → Connection → OpenClaw runs) | Keeping the agent on hardware that stays powered on. Runbook: macOS remote access. |
| Laptop | Laptop, exposed safely via SSH tunnel or Tailscale Serve (keep gateway.bind: "loopback") | Single-machine setups. See Tailscale and Web. |
gateway.bind: "loopback" and using Tailscale Serve for the Control UI, or a trusted LAN/Tailnet bind with gateway.remote.transport: "direct". SSH tunnel is the fallback that works from any machine.
Command flow (what runs where)
One Gateway owns state and channels; nodes are peripherals. Example (Telegram message routed to a node tool):- Telegram message arrives at the Gateway.
- Gateway runs the agent, which decides whether to call a node tool.
- Gateway calls the node over the Gateway WebSocket (
node.invokeRPC). - Node returns the result; Gateway replies to Telegram.
SSH tunnel (CLI + tools)
openclaw health and openclaw status --deep reach the remote Gateway via ws://127.0.0.1:18789. openclaw gateway status, openclaw gateway health, openclaw gateway probe, and openclaw gateway call can also target a forwarded URL via --url.
Replace
18789 with your configured gateway.port (or --port / OPENCLAW_GATEWAY_PORT).CLI remote defaults
Persist a remote target so CLI commands use it by default:ws://127.0.0.1:18789 and open the SSH tunnel first. In the macOS app’s SSH-tunnel transport, the discovered Gateway hostname goes in gateway.remote.sshTarget (user@host or user@host:port); gateway.remote.url stays the local tunnel URL. If the remote port differs from the local one, set gateway.remote.remotePort.
Host-key verification is strict by default (gateway.remote.sshHostKeyPolicy: "strict"). Set it to "openssh" to delegate to your effective OpenSSH config instead; review your user and system SSH settings before enabling it.
For a Gateway already reachable on a trusted LAN or Tailnet, use direct mode:
Credential precedence
Gateway credential resolution follows one shared contract across call/probe/status paths and Discord exec-approval monitoring. Node-host uses the same contract with one local-mode exception (it ignoresgateway.remote.*).
- Explicit credentials (
--token,--password, or a tool’sgatewayToken) always win on call paths that accept explicit auth. - URL override safety:
- CLI
--urlnever reuses implicit config/env credentials. - Env
OPENCLAW_GATEWAY_URLmay use env credentials only (OPENCLAW_GATEWAY_TOKEN/OPENCLAW_GATEWAY_PASSWORD).
- CLI
- Local mode defaults:
- token:
OPENCLAW_GATEWAY_TOKEN->gateway.auth.token->gateway.remote.token(remote fallback only when the local token is unset) - password:
OPENCLAW_GATEWAY_PASSWORD->gateway.auth.password->gateway.remote.password(remote fallback only when the local password is unset)
- token:
- Remote mode defaults:
- token:
gateway.remote.token->OPENCLAW_GATEWAY_TOKEN->gateway.auth.token - password:
OPENCLAW_GATEWAY_PASSWORD->gateway.remote.password->gateway.auth.password
- token:
- Node-host local-mode exception:
gateway.remote.token/gateway.remote.passwordare ignored. - Remote probe/status token checks are strict by default: they use
gateway.remote.tokenonly (no local token fallback) when targeting remote mode. - Gateway env overrides use
OPENCLAW_GATEWAY_*only.
Chat UI remote access
WebChat has no separate HTTP port; the SwiftUI chat UI connects directly to the Gateway WebSocket.- Forward
18789over SSH (see above), then connect clients tows://127.0.0.1:18789. - For LAN/Tailnet direct mode, connect clients to the configured private
ws://or securewss://URL. - On macOS, the app’s remote mode manages the selected transport automatically.
macOS app remote mode
The macOS menu bar app drives the same setup end-to-end: remote status checks, WebChat, and Voice Wake forwarding. Runbook: macOS remote access.Security rules (remote/VPN)
Keep the Gateway loopback-only unless you are sure you need a bind.- Loopback + SSH/Tailscale Serve is the safest default (no public exposure).
- Plaintext
ws://is accepted for loopback, private/LAN (RFC 1918), link-local, CGNAT,.local, and.ts.nethosts. Public remote hosts must usewss://. - Non-loopback binds (
lan/tailnet/custom, orautowhen loopback is unavailable) must use Gateway auth: token, password, or an identity-aware reverse proxy withgateway.auth.mode: "trusted-proxy". gateway.remote.token/.passwordare client credential sources; they do not configure server auth by themselves.- Local call paths can use
gateway.remote.*as a fallback only whengateway.auth.*is unset. - If
gateway.auth.token/gateway.auth.passwordis explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking). gateway.remote.tlsFingerprintpins the remote TLS cert forwss://, including macOS direct mode. Without a stored pin, macOS only pins on first use after normal system trust passes; self-signed or private-CA Gateways need an explicit fingerprint or Remote over SSH.- Tailscale Serve can authenticate Control UI/WebSocket traffic via identity headers when
gateway.auth.allowTailscale: true. HTTP API endpoints do not use that header auth and instead follow the Gateway’s normal HTTP auth mode. This tokenless flow assumes the Gateway host is trusted; set it tofalsefor shared-secret auth everywhere. - Trusted-proxy auth expects a non-loopback identity-aware proxy by default. Same-host loopback reverse proxies require explicit
gateway.auth.trustedProxy.allowLoopback = true. - Treat browser control like operator access: tailnet-only plus deliberate node pairing.
macOS: persistent SSH tunnel via LaunchAgent
For macOS clients, the easiest persistent setup uses an SSHLocalForward config entry plus a LaunchAgent that keeps the tunnel alive across reboots and crashes.
Step 1: add SSH config
Edit~/.ssh/config:
<REMOTE_IP> and <REMOTE_USER> with your values.
Step 2: copy SSH key (one-time)
Step 3: configure the gateway token
gateway.remote.password instead if the remote Gateway uses password auth. OPENCLAW_GATEWAY_TOKEN is still valid as a shell-level override, but the durable remote-client setup is gateway.remote.token / gateway.remote.password.
Step 4: create the LaunchAgent
Save as~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist:
Step 5: load the LaunchAgent
If you have a leftover
com.openclaw.ssh-tunnel LaunchAgent from an older setup, unload and delete it.Troubleshooting
| Config entry | What it does |
|---|---|
LocalForward 18789 127.0.0.1:18789 | Forwards local port 18789 to remote port 18789 |
ssh -N | SSH without executing remote commands (port forwarding only) |
KeepAlive | Restarts the tunnel automatically if it crashes |
RunAtLoad | Starts the tunnel when the LaunchAgent loads at login |