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):

  1. Collects each pane's root PID and walks descendants via a single /proc ppid map.
  2. Reads listening sockets from /proc/net/tcp and /proc/net/tcp6.
  3. Matches socket inodes to pane process trees.
  4. Diffs opens/closes → port-opened / port-closed events, 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
CommandPurpose
ports listTable 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_forward defaults off.
  • Put noisy ports/processes in ports.ignore / ports.ignore_processes.

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

SymptomCheck
No ports ever showLinux? ports.enabled? Process listening under the pane tree?
Wrong / noisy portsignore / ignore_processes; leave ignore_ephemeral on
ssh-cmd host wrongports.ssh_host or $SSH_CONNECTION / hostname
Forward failstailscale ip -4 works? Port already detected? AddrInUse?
Port gone after stopExpected — forwards are not restored across daemon restart