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.
Option 1 — LAN bind (same WiFi)
Section titled “Option 1 — LAN bind (same WiFi)”The simplest path. Edit your WORKFLOW.md:
server: host: 0.0.0.0 port: 8090Restart itervox. The dashboard is now reachable from any device on the same
network. Find your laptop’s LAN IP:
# macOS / Linuxipconfig getifaddr en0 # Wi-Fi on macOShostname -I # LinuxOn 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:
ssh -L 8090:localhost:8090 user@remote-hostThen 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.
Option 3 — Tailscale (recommended for remote work)
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.mdserver: host: 0.0.0.0 # bind to all interfaces — Tailscale handles auth + encryption port: 8090On 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.
Option 4 — ngrok (managed public URL)
Section titled “Option 4 — ngrok (managed public URL)”ngrok is the original tunnelling-as-a-service. One command and you get a public HTTPS URL that proxies to your local dashboard.
-
Install ngrok and authenticate (
ngrok config add-authtoken <token>). -
Start the Itervox daemon as usual (default
127.0.0.1:8090is fine — ngrok talks to localhost). Watch stderr for thedashboard 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. -
In a separate terminal:
Terminal window ngrok http 8090ngrok prints a public URL like
https://abcd-1234.ngrok-free.app. -
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.
Lock down the public URL
Section titled “Lock down the public 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:
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.
-
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 -
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:8001This opens an outbound connection to the Piko server and registers the endpoint name
itervox-dashboard. -
Configure DNS so that
itervox-dashboard.your-domain.com(or whichever subdomain you choose) points to the Piko server’s proxy port. -
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_TOKENin.itervox/.env— recommended for any Piko setup you’ll use repeatedly.
Why Piko fits Itervox
Section titled “Why Piko fits Itervox”- 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.
Reverse proxy / TLS termination
Section titled “Reverse proxy / TLS termination”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.
Comparison
Section titled “Comparison”| Option | Same WiFi | Remote | Self-hosted | Setup | Best for |
|---|---|---|---|---|---|
| LAN bind | ✅ | ❌ | ✅ | 1 min | Home / office, single user |
| SSH tunnel | ✅ | ✅ | ✅ | 2 min | Developers SSHing into a server |
| Tailscale | ✅ | ✅ | ⚠️ third-party | 5 min | Personal use, multi-device |
| ngrok | ✅ | ✅ | ❌ SaaS | 2 min | Quickest public URL, demos |
| Piko | ✅ | ✅ | ✅ | 30 min | Teams, vendor-independent setups |
The API token
Section titled “The API token”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_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.
Opting out of bearer auth
Section titled “Opting out of bearer auth”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: trueallow_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.netThis 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.