---
title: Remote Access
description: Access the AO dashboard from another device or over Tailscale. Covers ports, binding, reverse proxies, power management, and security.
---

# Remote Access

By default the AO dashboard binds to `localhost:3000` and is only accessible from the machine it runs on. This page explains how to access it from another device — your phone, a second laptop, or a remote machine — using Tailscale or direct network binding.

## Tailscale (recommended)

[Tailscale](https://tailscale.com) creates a private WireGuard mesh network between your devices. Every device gets a stable IP like `100.x.x.x` and a DNS name like `my-laptop.tail1234.ts.net`. No port forwarding, no firewall rules required.

### Setup

1. Install Tailscale on both the machine running AO and the device you want to access it from.

   ```bash
   # macOS
   brew install --cask tailscale

   # Ubuntu / Debian
   curl -fsSL https://tailscale.com/install.sh | sh
   ```

2. Start Tailscale and authenticate:

   ```bash
   sudo tailscale up
   ```

3. Find your machine's Tailscale IP:

   ```bash
   tailscale ip -4
   # e.g. 100.64.0.1
   ```

4. Bind AO to all interfaces so Tailscale traffic can reach it:

   ```yaml
   # agent-orchestrator.yaml
   port: 3000
   ```

   Then start AO with `HOST=0.0.0.0` so it listens on all interfaces (not just localhost):

   ```bash
   HOST=0.0.0.0 ao start
   ```

   Access the dashboard from another Tailscale device at:

   ```
   http://100.64.0.1:3000
   ```

   Or using the MagicDNS hostname (if you have MagicDNS enabled in your Tailnet):

   ```
   http://my-laptop.tail1234.ts.net:3000
   ```

### Tailscale serve (optional — HTTPS)

For HTTPS with a valid certificate, use `tailscale serve`:

```bash
tailscale serve https:443 / http://localhost:3000
```

This makes the dashboard available at `https://my-laptop.tail1234.ts.net` with a Let's Encrypt certificate managed by Tailscale. The WebSocket connections for the terminal also work through Tailscale serve.

## Binding to a specific interface

AO uses Next.js for the dashboard. To bind to all interfaces (required for any remote access without Tailscale serve), set the `HOST` environment variable:

```bash
# Bind to all interfaces
HOST=0.0.0.0 ao start
```

Or set it in your shell profile:

```bash
export HOST=0.0.0.0
ao start
```

To bind to a specific IP only (e.g. your Tailscale IP):

```bash
HOST=100.64.0.1 ao start
```

The terminal WebSocket ports (`terminalPort` and `directTerminalPort`) also need to be reachable. If you're using Tailscale, the mesh handles this transparently as long as the ports are not firewalled locally.

## macOS: preventing sleep

If you run AO on a Mac, the machine going to sleep will kill the agents and the dashboard. Use `caffeinate` to prevent sleep while AO is running:

```bash
caffeinate -i ao start
```

`caffeinate -i` prevents idle sleep (triggered by inactivity) but still allows display sleep. For a machine you want to run headlessly overnight:

```bash
# Prevent all sleep (including display)
caffeinate -dims ao start
```

To run AO persistently in the background as a launchd service, create a plist at `~/Library/LaunchAgents/com.ao.orchestrator.plist`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.ao.orchestrator</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/ao</string>
    <string>start</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>EnvironmentVariables</key>
  <dict>
    <key>HOST</key>
    <string>0.0.0.0</string>
  </dict>
</dict>
</plist>
```

Load it with:

```bash
launchctl load ~/Library/LaunchAgents/com.ao.orchestrator.plist
```

## Reverse proxy

If you want to expose AO over a domain name with TLS, or place it behind an authentication layer, you can front it with nginx or Caddy.

**Important:** AO uses two WebSocket servers (one for tmux-attached terminals, one for direct PTY terminals). Your proxy must forward HTTP upgrade headers for both.

### Environment variables for proxied setups

| Variable | Purpose |
|----------|---------|
| `HOST=0.0.0.0` | Bind the Next.js dashboard to all interfaces |
| `TERMINAL_PORT` | Override the tmux WS server port (server-side) |
| `DIRECT_TERMINAL_PORT` | Override the direct PTY WS server port (server-side) |
| `NEXT_PUBLIC_TERMINAL_WS_PATH` | Override the WebSocket base path the browser client dials — required when the proxy rewrites the path |

### nginx

```nginx
server {
  listen 443 ssl;
  server_name ao.example.com;

  # SSL cert config here

  # Dashboard
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  # Terminal WebSockets — tmux mux
  location /ws/terminal/ {
    proxy_pass http://127.0.0.1:14800/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
  }

  # Terminal WebSockets — direct PTY
  location /ws/direct/ {
    proxy_pass http://127.0.0.1:14801/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400;
  }
}
```

Then start AO with the matching path env var so the browser client dials through the proxy:

```bash
HOST=0.0.0.0 NEXT_PUBLIC_TERMINAL_WS_PATH=/ws/terminal ao start
```

### Caddy

Caddy handles WebSocket upgrades automatically — no explicit `Upgrade` headers needed:

```
ao.example.com {
  reverse_proxy /ws/terminal/* 127.0.0.1:14800
  reverse_proxy /ws/direct/* 127.0.0.1:14801
  reverse_proxy 127.0.0.1:3000
}
```

Start AO the same way:

```bash
HOST=0.0.0.0 NEXT_PUBLIC_TERMINAL_WS_PATH=/ws/terminal ao start
```

### Pinning WebSocket ports

By default AO auto-detects available ports for the WebSocket servers starting at 14800/14801. To pin them (required when configuring a reverse proxy), set them in `agent-orchestrator.yaml`:

```yaml
port: 3000
terminalPort: 14800
directTerminalPort: 14801
```

Or pass them as environment variables when starting:

```bash
HOST=0.0.0.0 TERMINAL_PORT=14800 DIRECT_TERMINAL_PORT=14801 ao start
```

## Accessing the dashboard from mobile

The AO dashboard is a responsive web app — it works on mobile browsers. Connect over Tailscale and open the URL in Safari or Chrome. The Kanban board and session detail views are usable on a phone screen.

**Limitations on mobile:**

- The built-in terminal (xterm.js) works but is difficult to type in on a touch screen. Use it to read agent output; for sending messages use the session detail input field.
- There is no native mobile app. Notifications go through your configured notifiers (Slack, desktop, etc.) — there is no push notification to the browser.

## Security considerations

**AO has no authentication.** Anyone who can reach the HTTP port can view all sessions, read terminal output, send messages to agents, and trigger merges. Never expose the dashboard port to the public internet.

Mitigations:

- Use Tailscale — the mesh is authenticated and encrypted end-to-end. Only your devices can reach the IP.
- If you must use a public host, put a reverse proxy with HTTP Basic Auth (nginx, Caddy) in front of AO. See the [Reverse proxy](#reverse-proxy) section above.
- Firewall the port at the OS level and only allow Tailscale traffic:

  ```bash
  # UFW example — allow only Tailscale interface
  sudo ufw allow in on tailscale0 to any port 3000
  sudo ufw deny 3000
  ```

**Environment variables and secrets** in agent processes are visible to anyone with dashboard access. Do not run AO on a shared machine without Tailscale or auth.

**Webhook endpoint:** If you expose the dashboard publicly and have GitHub (or another tracker) configured, the `/api/webhooks` endpoint receives push events from GitHub. This endpoint is protected by a webhook secret configured in your tracker settings — verify the secret is set before exposing the port publicly.

For more on project identity, local config, and runtime data, see [Configuration](/docs/configuration).

## Port reference

| Port | Default | Config key | Env var override | Purpose |
|------|---------|------------|-----------------|---------|
| `3000` | dashboard HTTP | `port` | `PORT` | Next.js app + API routes |
| `14800` | tmux terminal WS | `terminalPort` | `TERMINAL_PORT` | WebSocket for tmux-attached terminal |
| `14801` | direct terminal WS | `directTerminalPort` | `DIRECT_TERMINAL_PORT` | WebSocket for direct PTY terminal |

If you run multiple AO instances on the same machine, change all three ports to avoid `EADDRINUSE` errors:

```yaml
# Second AO instance
port: 3001
terminalPort: 14810
directTerminalPort: 14811
```

Note: When `terminalPort` and `directTerminalPort` are not set in the config or as env vars, AO auto-detects a free port pair starting from 14800. Set them explicitly whenever you configure a reverse proxy or firewall rules.

## See also

- [Configuration](/docs/configuration) — global registry and local project config
- [Projects](/docs/configuration/projects) — per-project behavior options
- [Troubleshooting](/docs/troubleshooting) — connection and port issues
