Guides
Ports & forwarding
vmux watches processes under each pane and surfaces listening TCP ports they own — the
Vite server your agent just started, a local API on 3000, whatever is bound. Detection feeds
the sidebar, notifications, CLI helpers, and optional Tailscale forwarding.
Linux only for detection (reads /proc/net/tcp{,6} and
/proc/<pid>/fd). On macOS the ports list is empty. No ss dependency for
the main scanner.
How detection works
A dedicated background loop (default every ports.poll_secs, never the snapshot hot path):
- Collects each pane's root PID and walks descendants via a single
/procppid map. - Reads listening sockets from
/proc/net/tcpand/proc/net/tcp6. - Matches socket inodes to pane process trees.
- Diffs opens/closes →
port-opened/port-closedevents, optional notification, and workspace sidebar chips (host, port, pids, process, pane).
vmux ports list triggers an opportunistic rescan so you are not stuck waiting for the next
tick. The relay listen port (relay.port, default 4399) is always ignored.
vmux config set ports.enabled true # default
vmux config set ports.poll_secs 2
vmux config set ports.ignore 5432,6379 # never surface these
vmux config set ports.ignore_processes ssh,sshd
vmux config set ports.ignore_ephemeral true # default: hide kernel ephemeral range
Full key list: Configuration → ports.*. Array keys are comma-separated on the CLI; edit config.json for complex lists.
CLI
# List detected ports (optional workspace filter)
vmux ports list
vmux ports list --workspace ws-2 --json
# Print an ssh -L one-liner (run this on your laptop)
vmux ports ssh-cmd 5173
vmux config set ports.ssh_host 'user@devbox'
# Expose a detected port on the Tailscale interface (daemon TCP proxy)
vmux ports forward 3000
vmux ports forward 3000 --via tailscale
# Stop that forward
vmux ports unforward 3000
| Command | Purpose |
|---|---|
ports list | Table of port / process / pane / workspace / host / forward URL |
ports ssh-cmd <port> | Print ssh -L port:127.0.0.1:port user@host for local use |
ports forward <port> | Bind <tailscale-ip>:<port> → 127.0.0.1:<port> in the daemon |
ports unforward <port> | Tear down a Tailscale forward |
SSH path: ssh-cmd only prints a command — your local
ssh client must run it. The remote daemon cannot create -L forwards for you.
Tailscale path: ports forward starts an in-process proxy on the machine's
Tailscale IPv4. Requires tailscale online. Never binds 0.0.0.0. Forwards are
runtime-only; pane exit and unforward stop them.
Security
- Forwards never bind all interfaces — only the Tailscale IP.
- The phone relay is a separate surface and also refuses public binds. Don't confuse
relay.port(default 4399) with application ports (3000, 5173, …). auto_forwarddefaults off.- Put noisy ports/processes in
ports.ignore/ports.ignore_processes.
Sidebar and attach UI
When ports are present, the workspace detail row shows short labels (:3000). Open the panel
with the command palette → ports, or the prefix key — see
Keyboard shortcuts for the panel's keys.
Troubleshooting
| Symptom | Check |
|---|---|
| No ports ever show | Linux? ports.enabled? Process listening under the pane tree? |
| Wrong / noisy ports | ignore / ignore_processes; leave ignore_ephemeral on |
ssh-cmd host wrong | ports.ssh_host or $SSH_CONNECTION / hostname |
| Forward fails | tailscale ip -4 works? Port already detected? AddrInUse? |
| Port gone after stop | Expected — forwards are not restored across daemon restart |