2025-12-09 17:51:05 +00:00
---
2025-12-14 00:52:37 +00:00
summary: "Remote access using SSH tunnels (Gateway WS) and tailnets"
2025-12-09 17:51:05 +00:00
read_when:
- Running or troubleshooting remote gateway setups
2026-01-31 16:04:03 -05:00
title: "Remote Access"
2025-12-09 17:51:05 +00:00
---
2026-01-31 21:13:13 +09:00
2025-12-14 00:49:34 +00:00
# Remote access (SSH, tunnels, and tailnets)
2026-01-12 02:11:33 +00:00
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.
2025-12-14 00:49:34 +00:00
- For **operators (you / the macOS app)** : SSH tunneling is the universal fallback.
2026-01-22 23:12:38 +00:00
- For **nodes (iOS/Android and future devices)** : connect to the Gateway **WebSocket** (LAN/tailnet or SSH tunnel as needed).
2025-12-14 00:49:34 +00:00
## The core idea
2026-01-03 11:46:58 +01:00
- The Gateway WebSocket binds to **loopback** on your configured port (defaults to 18789).
2025-12-14 00:49:34 +00:00
- For remote use, you forward that loopback port over SSH (or use a tailnet/VPN and tunnel less).
2026-01-17 02:19:12 +00:00
## Common VPN/tailnet setups (where the agent lives)
Think of the **Gateway host** as “where the agent lives.” It owns sessions, auth profiles, channels, and state.
Your laptop/desktop (and nodes) connect to that host.
### 1) Always-on Gateway in your tailnet (VPS or home server)
Run the Gateway on a persistent host and reach it via **Tailscale** or SSH.
- **Best UX:** keep `gateway.bind: "loopback"` and use **Tailscale Serve** for the Control UI.
- **Fallback:** keep loopback + SSH tunnel from any machine that needs access.
2026-02-05 17:45:01 -05:00
- **Examples:** [exe.dev ](/install/exe-dev ) (easy VM) or [Hetzner ](/install/hetzner ) (production VPS).
2026-01-17 02:19:12 +00:00
This is ideal when your laptop sleeps often but you want the agent always-on.
### 2) Home desktop runs the Gateway, laptop is remote control
The laptop does **not** run the agent. It connects remotely:
2026-01-30 03:15:10 +01:00
- Use the macOS app’ s **Remote over SSH** mode (Settings → General → “OpenClaw runs”).
2026-01-17 02:19:12 +00:00
- The app opens and manages the tunnel, so WebChat + health checks “just work.”
Runbook: [macOS remote access ](/platforms/mac/remote ).
### 3) Laptop runs the Gateway, remote access from other machines
Keep the Gateway local but expose it safely:
- SSH tunnel to the laptop from other machines, or
- Tailscale Serve the Control UI and keep the Gateway loopback-only.
Guide: [Tailscale ](/gateway/tailscale ) and [Web overview ](/web ).
2026-01-11 03:17:06 +01:00
## Command flow (what runs where)
2026-01-21 17:45:12 +00:00
One gateway service owns state + channels. Nodes are peripherals.
2026-01-11 03:17:06 +01:00
Flow example (Telegram → node):
2026-01-31 21:13:13 +09:00
2026-01-11 03:17:06 +01:00
- Telegram message arrives at the **Gateway** .
- Gateway runs the **agent** and decides whether to call a node tool.
2026-01-22 23:12:38 +00:00
- Gateway calls the **node** over the Gateway WebSocket (`node.*` RPC).
2026-01-11 03:17:06 +01:00
- Node returns the result; Gateway replies back out to Telegram.
Notes:
2026-01-31 21:13:13 +09:00
2026-01-21 17:45:12 +00:00
- **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 )).
2026-01-22 23:12:38 +00:00
- macOS app “node mode” is just a node client over the Gateway WebSocket.
2026-01-11 03:17:06 +01:00
2025-12-14 00:49:34 +00:00
## SSH tunnel (CLI + tools)
Create a local tunnel to the remote Gateway WS:
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
With the tunnel up:
2026-01-31 21:13:13 +09:00
2026-01-30 03:15:10 +01:00
- `openclaw health` and `openclaw status --deep` now 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` when needed.
2025-12-14 00:49:34 +00:00
2026-01-30 03:15:10 +01:00
Note: replace `18789` with your configured `gateway.port` (or `--port` /`OPENCLAW_GATEWAY_PORT` ).
2026-02-04 18:59:44 -05:00
Note: when you pass `--url` , the CLI does not fall back to config or environment credentials.
Include `--token` or `--password` explicitly. Missing explicit credentials is an error.
2026-01-03 11:46:58 +01:00
2026-01-01 20:10:50 +01:00
## CLI remote defaults
You can persist a remote target so CLI commands use it by default:
```json5
{
gateway: {
mode: "remote",
remote: {
url: "ws://127.0.0.1:18789",
2026-01-31 21:13:13 +09:00
token: "your-token",
},
},
2026-01-01 20:10:50 +01:00
}
```
When the gateway is loopback-only, keep the URL at `ws://127.0.0.1:18789` and open the SSH tunnel first.
2025-12-17 23:05:28 +01:00
## Chat UI over SSH
2025-12-14 00:49:34 +00:00
2025-12-17 23:05:28 +01:00
WebChat no longer uses a separate HTTP port. The SwiftUI chat UI connects directly to the Gateway WebSocket.
2025-12-14 00:49:34 +00:00
2025-12-17 23:05:28 +01:00
- Forward `18789` over SSH (see above), then connect clients to `ws://127.0.0.1:18789` .
- On macOS, prefer the app’ s “Remote over SSH” mode, which manages the tunnel automatically.
2025-12-14 00:49:34 +00:00
## macOS app “Remote over SSH”
The macOS menu bar app can drive the same setup end-to-end (remote status checks, WebChat, and Voice Wake forwarding).
2026-01-10 14:51:21 -06:00
Runbook: [macOS remote access ](/platforms/mac/remote ).
2026-01-17 02:19:12 +00:00
## Security rules (remote/VPN)
Short version: **keep the Gateway loopback-only** unless you’ re sure you need a bind.
- **Loopback + SSH/Tailscale Serve** is the safest default (no public exposure).
2026-01-21 20:35:39 +00:00
- **Non-loopback binds** (`lan` /`tailnet` /`custom` , or `auto` when loopback is unavailable) must use auth tokens/passwords.
2026-01-17 02:19:12 +00:00
- `gateway.remote.token` is **only** for remote CLI calls — it does **not** enable local auth.
2026-01-20 12:20:20 +00:00
- `gateway.remote.tlsFingerprint` pins the remote TLS cert when using `wss://` .
2026-01-17 02:19:12 +00:00
- **Tailscale Serve** can authenticate via identity headers when `gateway.auth.allowTailscale: true` .
Set it to `false` if you want tokens/passwords instead.
2026-01-27 03:23:42 +00:00
- Treat browser control like operator access: tailnet-only + deliberate node pairing.
2026-01-17 02:19:12 +00:00
Deep dive: [Security ](/gateway/security ).