Remote Access
Remote access via SSH tunnels (Gateway WS) and tailnet.
This repository supports "remote over SSH" by running a single gateway (primary server) on a dedicated host (desktop/server), with clients connecting to it.
- For operators (you/macOS app): SSH tunneling is the universal fallback.
- For nodes (iOS/Android and future devices): connect to the gateway WebSocket (using LAN/tailnet or SSH tunnel as needed).
Core idea
- The gateway WebSocket binds to loopback on a configured port (default 18789).
- For remote use, you can forward that loopback port via SSH (or use tailnet/VPN to reduce tunnels).
Common VPN/tailnet setup (where the agent lives)
Think of the gateway host as "where the agent lives." It has sessions, auth profiles, channels, and state.
Laptops/desktops (and nodes) connect to that host.
#
1) Always-online gateway in tailnet (VPS or home server)
Run the gateway on a persistent host and access via Tailscale or SSH.
- ''Best UX:'' keep ''gateway.bind: "loopback"'' and use ''Tailscale Serve'' for the control UI.
- Fallback: maintain loopback + SSH tunnel from machines that need access.
- ''Examples:'' ''exe.dev'' (simple VM) or ''Hetzner'' (production VPS).
Great if your laptop sleeps often but you want the agent always online.
#
2) Run gateway on home desktop, remote control from laptop
The laptop does not run the agent. It connects remotely:
- Use the macOS app's Remote over SSH mode (Settings β General β "Where OpenClaw runs").
- The app opens and manages the tunnel so WebChat + health checks "just work."
Runbook: ''macOS remote access''.
#
3) Run gateway on laptop, remote access from other machines
Keep the gateway local but expose it safely:
- SSH tunnel from other machines to the laptop, or
- Tailscale serve for control UI and loopback-only gateway.
Guide: ''Tailscale'' and ''network overview''.
Command flow (what runs where)
A single gateway service owns state + channels. Nodes are peripherals.
Example flow (Telegram β node):
- Telegram message arrives at the gateway.
- Gateway runs the agent, decides whether to invoke node tools.
- Gateway invokes the ''node'' via gateway WebSocket (''node.*'' RPC).
- Node returns result. Gateway replies to Telegram.
Note:
- ''Nodes do not run the gateway service.'' One gateway per host unless you deliberately run isolated profiles (see ''multiple gateways'').
- The macOS app "node mode" is just a node client on the gateway WebSocket.
SSH tunnel (CLI + tools)
Create a local tunnel to a remote gateway WS:
ssh -N -L 18789:127.0.0.1:18789 user@host
Once the tunnel is established:
- ''openclaw health'' and ''openclaw status --deep'' reach the remote gateway via ''ws://127.0.0.1:18789''.
- openclaw gateway {status,health,send,agent,call}' can also target the forwarded URL via --url if needed.
Note: Replace ''18789'' with your configured ''gateway.port'' (or ''--port''/''OPENCLAW_GATEWAY_PORT'').
CLI remote defaults
You can persist a remote target for CLI commands to use by default:
{
gateway: {
mode: "remote",
remote: {
url: "ws://127.0.0.1:18789",
token: "your-token",
},
},
}If the gateway is loopback-only, keep the URL as ''ws://127.0.0.1:18789'' and open the SSH tunnel first.
Chat UI over SSH
WebChat no longer uses a separate HTTP port. The SwiftUI chat UI connects directly to the gateway WebSocket.
- Forward ''18789'' via SSH (see above), then connect the client to ''ws://127.0.0.1:18789''.
- On macOS, prefer the app's "Remote over SSH" mode which manages the tunnel automatically.
macOS app "Remote over SSH"
The macOS menu bar app can drive the same setup end-to-end (remote health checks, WebChat, voice wake forwarding).
Runbook: ''macOS remote access''.
Security rules (remote/VPN)
TL;DR: keep the gateway loopback-only unless you need to bind otherwise.
- Loopback + SSH/Tailscale Serve is the safest default (no public exposure).
- ''Non-loopback binding'' (''lan''/''tailnet''/''custom'' or ''auto'' when loopback isn't available) requires auth token/password.
- ''gateway.remote.token'' is for remote CLI calls ''only'' β it doesn't enable local auth.
- If using ''wss://'', ''gateway.remote.tlsFingerprint'' pins the remote TLS certificate.
- ''Tailscale Serve'' can authenticate via identity headers if ''gateway.auth.allowTailscale: true''.
Set to ''false'' if you want token/password auth required.
- Treat browser control as operator access: tailnet-only + intentional node pairing.
Details: ''Security''.