Skip to content

Remote Access & Mobile Dashboard

By default Itervox binds the HTTP server to 127.0.0.1:8090 — only reachable from the same machine that runs the daemon. To view the dashboard from your phone or another computer, you have five options.


The simplest path. Edit your WORKFLOW.md:

server:
host: 0.0.0.0
port: 8090

Restart itervox. The dashboard is now reachable from any device on the same network. Find your laptop’s LAN IP:

Terminal window
# macOS / Linux
ipconfig getifaddr en0 # Wi-Fi on macOS
hostname -I # Linux

On your phone, browse to http://<laptop-ip>:8090/?token=<your-token> — this matches the URL the daemon prints at startup. The dashboard will capture the token on first load and strip it from the URL.

Pros: zero setup, works on home/office WiFi, real-time SSE streaming. Cons: only works on the same network. Doesn’t survive when your phone leaves WiFi or your laptop sleeps.


Option 2 — SSH tunnel (developer-friendly)

Section titled “Option 2 — SSH tunnel (developer-friendly)”

If you’re already SSHing into a machine that runs Itervox, forward the port:

Terminal window
ssh -L 8090:localhost:8090 user@remote-host

Then browse to http://localhost:8090 in your local browser. On iOS/Android, apps like Termius and Blink Shell support port forwarding.

Pros: familiar to developers, no daemon config changes needed. Cons: mobile SSH clients are awkward; the tunnel dies when the SSH session disconnects.


Section titled “Option 3 — Tailscale (recommended for remote work)”

Tailscale is a zero-config WireGuard mesh VPN. Install it on both your laptop and your phone, log into the same account, and your phone can reach the laptop directly via its <laptop-name>.<tailnet>.ts.net hostname — from anywhere in the world.

# WORKFLOW.md
server:
host: 0.0.0.0 # bind to all interfaces — Tailscale handles auth + encryption
port: 8090

On your phone: http://laptop.tail-scale-name.ts.net:8090/?token=<your-token> on first visit. The dashboard captures the token and subsequent visits work without the query parameter.

Pros: works from anywhere, end-to-end encrypted, no port forwarding, no public exposure. NAT-traversal is automatic. Cons: requires installing Tailscale on every device; the free tier is generous but is a third-party dependency.


ngrok is the original tunnelling-as-a-service. One command and you get a public HTTPS URL that proxies to your local dashboard.

  1. Install ngrok and authenticate (ngrok config add-authtoken <token>).

  2. Start the Itervox daemon as usual (default 127.0.0.1:8090 is fine — ngrok talks to localhost). Watch stderr for the dashboard URL (carries token — copy/paste once) line (printed when stderr is a terminal; otherwise read <logs-dir>/api-token) and copy the token — the daemon auto-generates one regardless of the loopback bind, since ngrok is about to make this loopback address reachable from the public internet.

  3. In a separate terminal:

    Terminal window
    ngrok http 8090

    ngrok prints a public URL like https://abcd-1234.ngrok-free.app.

  4. On your phone, open https://abcd-1234.ngrok-free.app/?token=<token-you-copied-from-step-2> on first visit — the dashboard will capture the token and strip it from the URL.

The free tier exposes a random URL on every restart. Use ngrok’s built-in basic auth or OAuth to keep random visitors out:

Terminal window
ngrok http 8090 --basic-auth="you:strongpassword"

Or restrict by IP / Google email on a paid plan. The Itervox bearer token is already required on every request by default — ngrok’s auth options add a second layer in front of it, not a substitute for it. Pinning ITERVOX_API_TOKEN in .itervox/.env is recommended so the token stays stable across daemon restarts instead of rotating (and needing to be re-copied) every time.

Pros: the fastest path to a public URL, automatic HTTPS, works through any NAT or firewall, ships with built-in auth options. Cons: depends on a paid SaaS for stable subdomains; free-tier URLs change on every restart; a third-party sees your traffic in transit (though it’s end-to-end TLS to your machine).


Option 5 — Piko (self-hosted reverse tunnel)

Section titled “Option 5 — Piko (self-hosted reverse tunnel)”

Piko is an open-source, MIT-licensed reverse tunnel that you can self-host. It works like ngrok or Cloudflare Tunnel, but the server is yours — no SaaS, no rate limits, no third-party in the data path.

This is the recommended option if you want a public URL for your dashboard without depending on any vendor.

  1. Run a Piko server somewhere with a public IP (a $5 VPS works fine). Piko ships as a single Go binary and supports clustering for high availability if you need it.

    Terminal window
    piko server --proxy.bind-addr :8000 --upstream.bind-addr :8001
  2. On your laptop, start Itervox as usual and copy the token from the dashboard URL (carries token — copy/paste once) line on stderr (printed when stderr is a terminal; otherwise read <logs-dir>/api-token) — the daemon auto-generates one by default even on a loopback bind, since Piko is about to expose it publicly. Then run the Piko agent pointing at your local Itervox dashboard:

    Terminal window
    piko agent http itervox-dashboard 8090 \
    --connect.url ws://your-piko-server:8001

    This opens an outbound connection to the Piko server and registers the endpoint name itervox-dashboard.

  3. Configure DNS so that itervox-dashboard.your-domain.com (or whichever subdomain you choose) points to the Piko server’s proxy port.

  4. On your phone, browse to http://itervox-dashboard.your-domain.com/?token=<token-you-copied-in-step-2> on first visit. The dashboard captures the token, and subsequent visits work without the query parameter. The Piko server routes the request through the outbound tunnel to your laptop’s Itervox dashboard.

    For a token that survives daemon restarts (so you don’t have to re-copy it every time), pin ITERVOX_API_TOKEN in .itervox/.env — recommended for any Piko setup you’ll use repeatedly.

  • MIT-licensed and self-hostable — same ethos as Itervox itself. No vendor in the loop, no SaaS dependency, no rate limits.
  • Outbound-only tunnels — your laptop never needs an inbound port open or port forwarding on your home router.
  • Survives network changes — agents reconnect automatically when your laptop changes networks (coffee shop → home → office).
  • Single binary — fits the Itervox philosophy of “one binary, one config file.”

Pros: completely self-hosted, no vendor lock-in, public URL that survives network changes, free apart from VPS hosting. Cons: requires a small VPS with a public IP and a domain name; one-time setup is more involved than Tailscale.


If you put nginx, Caddy, or any other reverse proxy in front of the daemon — for example to terminate TLS on a cloud VM before forwarding to a loopback itervox process — the proxy rides on top of the same bearer auth described above. The proxy does not replace it, and it cannot see or check the token on your behalf unless you specifically configure it to.

Never set server.allow_unauthenticated: true on a proxied deployment. The daemon has no way to tell that a request arriving on 127.0.0.1 came through your TLS-terminating proxy rather than from an arbitrary local process — from the daemon’s point of view a proxied deployment and a bare loopback bind look identical. Leave auth on and pass the Authorization: Bearer header (and the ?token= query param on first load) through the proxy unmodified.


OptionSame WiFiRemoteSelf-hostedSetupBest for
LAN bind✅❌✅1 minHome / office, single user
SSH tunnel✅✅✅2 minDevelopers SSHing into a server
Tailscale✅✅⚠️ third-party5 minPersonal use, multi-device
ngrok✅✅❌ SaaS2 minQuickest public URL, demos
Piko✅✅✅30 minTeams, vendor-independent setups

Itervox is secure by default on every bind, including loopback: unless you’ve set ITERVOX_API_TOKEN yourself, the daemon auto-generates a random 32-byte token on startup and installs bearer-token authentication — regardless of server.host. This is deliberate: bind address is not a reliable signal for exposure. A daemon bound to 127.0.0.1 behind ngrok, Piko, Tailscale, or a reverse proxy is exactly as reachable from outside as one bound to 0.0.0.0, and the daemon has no way to detect that from inside the process — so it always generates a token rather than guessing. When stderr is a terminal, the token is printed once on stderr inside a dashboard URL that embeds it (this line never reaches the rotating log file):

INFO server: auto-generated ephemeral API token host=127.0.0.1 hint="set ITERVOX_API_TOKEN in .itervox/.env to pin a stable token, or set server.allow_unauthenticated: true to opt out"
INFO dashboard URL (carries token — copy/paste once) url=http://127.0.0.1:8090/?token=<long-hex-token>

When stderr is not a terminal (systemd, a container, itervox 2>file), the tokenised URL is withheld so it never lands in journald or a log collector. The daemon instead logs dashboard URL (token withheld from logs) with the token-free URL, a sha256: fingerprint, and — for an auto-generated token — token_file=<logs-dir>/api-token. That file (mode 0600, rewritten on every start) holds the token; <logs-dir> defaults to a per-project directory under ~/.itervox/logs (the same directory as itervox.log) and is overridden by --logs-dir. If you pinned ITERVOX_API_TOKEN, use that value — no file is written, and any stale api-token is removed. Set ITERVOX_PRINT_TOKEN=1 to print the tokenised URL on stderr anyway; ITERVOX_PRINT_TOKEN=0 or --no-print-token suppresses it even on a terminal.

Auto-generated tokens are ephemeral — they change on every restart, which means the URL above changes too. Pinning a stable token in .itervox/.env is recommended so the token survives restarts — a bookmark or shortcut on your phone keeps working instead of needing the ?token= value re-copied from stderr every time you restart the daemon. .itervox/.env is loaded automatically on startup and survives shell restarts and launcher scripts that wouldn’t inherit an exported variable:

.itervox/.env
ITERVOX_API_TOKEN=$(openssl rand -hex 32)

An export ITERVOX_API_TOKEN=… in your shell also works, but only if you launch itervox from that same shell session.

If you really want an unauthenticated daemon — for example, on an air-gapped LAN or behind a strict firewall where you accept the risk — set the explicit opt-out in WORKFLOW.md:

server:
host: 0.0.0.0
port: 8090
allow_unauthenticated: true

allow_unauthenticated was previously named allow_unauthenticated_lan; the old key still parses (with a deprecation warning logged at startup) but new configs should use allow_unauthenticated. Setting it to true disables auth entirely — no bearer check is installed on any bind, not just non-loopback ones — and Itervox logs a warning at startup naming the risk. In its place a cross-origin guard refuses state-changing requests (POST, PUT, PATCH, DELETE) that a browser marks cross-site, or whose Origin host and port differ from Host, with 403 cross_origin_forbidden, so a malicious page in your browser cannot drive the daemon. curl and scripts (no Origin header) are unaffected; see the configuration reference for the exact rule.

It also installs a Host guard against DNS rebinding (a page on the attacker’s hostname re-pointed at your daemon’s address, which the cross-origin guard sees as same-origin): every route answers only a Host of localhost, an IP address, the configured server.host, or a name listed in server.allowed_hosts, and refuses anything else with 403 host_not_allowed (GET /api/v1/health and GET /api/v1/ready are exempt). Options 1 (LAN by IP) and 2 (SSH tunnel to localhost) work unchanged, as does Tailscale by its 100.x address. If you browse by a name — a Tailscale MagicDNS name, a tunnel hostname, a container service name — add it:

server:
host: 0.0.0.0
allow_unauthenticated: true
allowed_hosts:
- devbox.tail1234.ts.net

This flag does nothing when ITERVOX_API_TOKEN is set — an explicit token always wins, and auth stays on. Do not combine this flag with any of the options above except Option 1/2/3 on a fully trusted network — see the reverse-proxy/TLS-termination note above for why it must never be set on a tunneled or proxied deployment.

When ITERVOX_API_TOKEN is set and stderr is a terminal, the daemon prints a dashboard URL that embeds the token (http://host:8090/?token=…) on stderr — copy/paste that URL into your browser once (headless, append ?token=<your pinned value> yourself). The dashboard captures the token into sessionStorage, strips it from the URL bar via history.replaceState, and sends it as Authorization: Bearer on every subsequent request (including SSE streams). If the token is missing, wrong, or the session ends, the dashboard shows a login screen with a paste input.