{
  "title": "Remote access (SSH, tunnels, and tailnets)",
  "content": "This repo supports “remote over SSH” by keeping a single Gateway (the master) running on a dedicated host (desktop/server) and connecting clients to it.\n\n* For **operators (you / the macOS app)**: SSH tunneling is the universal fallback.\n* For **nodes (iOS/Android and future devices)**: connect to the Gateway **WebSocket** (LAN/tailnet or SSH tunnel as needed).\n\n* The Gateway WebSocket binds to **loopback** on your configured port (defaults to 18789).\n* For remote use, you forward that loopback port over SSH (or use a tailnet/VPN and tunnel less).\n\n## Common VPN/tailnet setups (where the agent lives)\n\nThink of the **Gateway host** as “where the agent lives.” It owns sessions, auth profiles, channels, and state.\nYour laptop/desktop (and nodes) connect to that host.\n\n### 1) Always-on Gateway in your tailnet (VPS or home server)\n\nRun the Gateway on a persistent host and reach it via **Tailscale** or SSH.\n\n* **Best UX:** keep `gateway.bind: \"loopback\"` and use **Tailscale Serve** for the Control UI.\n* **Fallback:** keep loopback + SSH tunnel from any machine that needs access.\n* **Examples:** [exe.dev](/platforms/exe-dev) (easy VM) or [Hetzner](/platforms/hetzner) (production VPS).\n\nThis is ideal when your laptop sleeps often but you want the agent always-on.\n\n### 2) Home desktop runs the Gateway, laptop is remote control\n\nThe laptop does **not** run the agent. It connects remotely:\n\n* Use the macOS app’s **Remote over SSH** mode (Settings → General → “OpenClaw runs”).\n* The app opens and manages the tunnel, so WebChat + health checks “just work.”\n\nRunbook: [macOS remote access](/platforms/mac/remote).\n\n### 3) Laptop runs the Gateway, remote access from other machines\n\nKeep the Gateway local but expose it safely:\n\n* SSH tunnel to the laptop from other machines, or\n* Tailscale Serve the Control UI and keep the Gateway loopback-only.\n\nGuide: [Tailscale](/gateway/tailscale) and [Web overview](/web).\n\n## Command flow (what runs where)\n\nOne gateway service owns state + channels. Nodes are peripherals.\n\nFlow example (Telegram → node):\n\n* Telegram message arrives at the **Gateway**.\n* Gateway runs the **agent** and decides whether to call a node tool.\n* Gateway calls the **node** over the Gateway WebSocket (`node.*` RPC).\n* Node returns the result; Gateway replies back out to Telegram.\n\n* **Nodes do not run the gateway service.** Only one gateway should run per host unless you intentionally run isolated profiles (see [Multiple gateways](/gateway/multiple-gateways)).\n* macOS app “node mode” is just a node client over the Gateway WebSocket.\n\n## SSH tunnel (CLI + tools)\n\nCreate a local tunnel to the remote Gateway WS:\n\n* `openclaw health` and `openclaw status --deep` now reach the remote gateway via `ws://127.0.0.1:18789`.\n* `openclaw gateway {status,health,send,agent,call}` can also target the forwarded URL via `--url` when needed.\n\nNote: replace `18789` with your configured `gateway.port` (or `--port`/`OPENCLAW_GATEWAY_PORT`).\nNote: when you pass `--url`, the CLI does not fall back to config or environment credentials.\nInclude `--token` or `--password` explicitly. Missing explicit credentials is an error.\n\n## CLI remote defaults\n\nYou can persist a remote target so CLI commands use it by default:\n\nWhen the gateway is loopback-only, keep the URL at `ws://127.0.0.1:18789` and open the SSH tunnel first.\n\nWebChat no longer uses a separate HTTP port. The SwiftUI chat UI connects directly to the Gateway WebSocket.\n\n* Forward `18789` over SSH (see above), then connect clients to `ws://127.0.0.1:18789`.\n* On macOS, prefer the app’s “Remote over SSH” mode, which manages the tunnel automatically.\n\n## macOS app “Remote over SSH”\n\nThe macOS menu bar app can drive the same setup end-to-end (remote status checks, WebChat, and Voice Wake forwarding).\n\nRunbook: [macOS remote access](/platforms/mac/remote).\n\n## Security rules (remote/VPN)\n\nShort version: **keep the Gateway loopback-only** unless you’re sure you need a bind.\n\n* **Loopback + SSH/Tailscale Serve** is the safest default (no public exposure).\n* **Non-loopback binds** (`lan`/`tailnet`/`custom`, or `auto` when loopback is unavailable) must use auth tokens/passwords.\n* `gateway.remote.token` is **only** for remote CLI calls — it does **not** enable local auth.\n* `gateway.remote.tlsFingerprint` pins the remote TLS cert when using `wss://`.\n* **Tailscale Serve** can authenticate via identity headers when `gateway.auth.allowTailscale: true`.\n  Set it to `false` if you want tokens/passwords instead.\n* Treat browser control like operator access: tailnet-only + deliberate node pairing.\n\nDeep dive: [Security](/gateway/security).",
  "code_samples": [
    {
      "code": "With the tunnel up:\n\n* `openclaw health` and `openclaw status --deep` now reach the remote gateway via `ws://127.0.0.1:18789`.\n* `openclaw gateway {status,health,send,agent,call}` can also target the forwarded URL via `--url` when needed.\n\nNote: replace `18789` with your configured `gateway.port` (or `--port`/`OPENCLAW_GATEWAY_PORT`).\nNote: when you pass `--url`, the CLI does not fall back to config or environment credentials.\nInclude `--token` or `--password` explicitly. Missing explicit credentials is an error.\n\n## CLI remote defaults\n\nYou can persist a remote target so CLI commands use it by default:",
      "language": "unknown"
    }
  ],
  "headings": [
    {
      "level": "h2",
      "text": "The core idea",
      "id": "the-core-idea"
    },
    {
      "level": "h2",
      "text": "Common VPN/tailnet setups (where the agent lives)",
      "id": "common-vpn/tailnet-setups-(where-the-agent-lives)"
    },
    {
      "level": "h3",
      "text": "1) Always-on Gateway in your tailnet (VPS or home server)",
      "id": "1)-always-on-gateway-in-your-tailnet-(vps-or-home-server)"
    },
    {
      "level": "h3",
      "text": "2) Home desktop runs the Gateway, laptop is remote control",
      "id": "2)-home-desktop-runs-the-gateway,-laptop-is-remote-control"
    },
    {
      "level": "h3",
      "text": "3) Laptop runs the Gateway, remote access from other machines",
      "id": "3)-laptop-runs-the-gateway,-remote-access-from-other-machines"
    },
    {
      "level": "h2",
      "text": "Command flow (what runs where)",
      "id": "command-flow-(what-runs-where)"
    },
    {
      "level": "h2",
      "text": "SSH tunnel (CLI + tools)",
      "id": "ssh-tunnel-(cli-+-tools)"
    },
    {
      "level": "h2",
      "text": "CLI remote defaults",
      "id": "cli-remote-defaults"
    },
    {
      "level": "h2",
      "text": "Chat UI over SSH",
      "id": "chat-ui-over-ssh"
    },
    {
      "level": "h2",
      "text": "macOS app “Remote over SSH”",
      "id": "macos-app-“remote-over-ssh”"
    },
    {
      "level": "h2",
      "text": "Security rules (remote/VPN)",
      "id": "security-rules-(remote/vpn)"
    }
  ],
  "url": "llms-txt#remote-access-(ssh,-tunnels,-and-tailnets)",
  "links": []
}