Quick start
Your first tunnel in thirty seconds, then background sessions, multiple ports, and reading the states mirrorball reports.
You need mirrorball (mirb) on your PATH (install it) and a host you
can already ssh into. That second condition is the only one mirrorball adds nothing to: it
runs the ssh binary you already have, so if ssh myhost works, everything below works.
Thirty seconds
Forward one port:
$ mirb 10.0.0.7 3000 mirb ⇄ 10.0.0.7 up 4s
● localhost:3000 ← localhost:3000 ready
ssh 22030 · reconnects 0 · ^C to stoplocalhost:3000 on your machine now reaches localhost:3000 on 10.0.0.7. The filled
circle and the word ready are the point: mirrorball opened a real connection through the
tunnel and something answered. That is a stronger claim than ssh -L ever makes.
Ctrl-C stops it.
Several ports at once
Positional arguments, in any number, in any of the supported forms:
$ mirb 10.0.0.7 3000 8080:80 5432:db.internal:5432 mirb ⇄ 10.0.0.7 up 4s
● localhost:3000 ← localhost:3000 ready
● localhost:8080 ← localhost:80 ready
● localhost:5432 ← db.internal:5432 ready
ssh 54287 · reconnects 0 · ^C to stop3000— same port on both ends.8080:80— local 8080, remote 80. Use this whenever the remote port is privileged; binding below 1024 locally needs root, and mirrorball will tell you so rather than let ssh fail after authenticating.5432:db.internal:5432— reaches a third host,db.internal, as seen from10.0.0.7. This is how you get at a database that is not on the box you can ssh to.
Ranges work too: mirb 10.0.0.7 3000-3005. The complete grammar is in
Port syntax.
When the far end is not there
$ mirb 10.0.0.7 3000 8080:80 mirb ⇄ 10.0.0.7 up 2s
○ localhost:3000 ← localhost:3000 refused
○ localhost:8080 ← localhost:80 refused
ssh 20769 · reconnects 0 · ^C to stopHollow circles, and the word refused. This is not a broken tunnel — the tunnel is
perfect. Nothing is listening on 10.0.0.7 at those ports. Go start your server; mirrorball
will report ready the next time it probes.
Telling those two failures apart is the entire reason mirrorball exists. See the three readiness states for how the distinction is made and where it can be wrong.
Leave it running
--background (or -b) detaches the tunnel from your shell:
$ mirb --background --name api 10.0.0.7 3000 8080:80 lxa2xk 10.0.0.7 ready
● localhost:3000 ← localhost:3000 ready
● localhost:8080 ← localhost:80 ready
stop it with: mirb stop lxa2xkmirrorball does not return until the detached supervisor has proved the tunnel works. A flag that returned the moment the child was spawned would hand you a session id and a set of ports that are not listening yet — and the first thing anyone does with that id is connect.
lxa2xk is the session's short id. It survives your terminal closing.
Manage what is running
$ mirb ls ID NAME HOST FORWARDS UP STATUS
lxa2xk api 10.0.0.7 3000 ← 3000, 8080 ← 80 1s ● ready$ mirb logs api2026-08-19T08:42:27.910Z mirb: 10.0.0.7: 2 forwards
2026-08-19T08:42:27.911Z mirb: localhost:3000 <- localhost:3000
2026-08-19T08:42:27.911Z mirb: localhost:8080 <- localhost:80
2026-08-19T08:42:27.911Z mirb: session starting (2 pending)
2026-08-19T08:42:27.969Z mirb: localhost:3000 <- localhost:3000 probing
2026-08-19T08:42:27.969Z mirb: session connecting (1 probing, 1 pending)
2026-08-19T08:42:27.970Z mirb: localhost:8080 <- localhost:80 probing
2026-08-19T08:42:27.970Z mirb: session connecting (2 probing)
2026-08-19T08:42:28.723Z mirb: localhost:3000 <- localhost:3000 ready
2026-08-19T08:42:28.723Z mirb: session connecting (1 ready, 1 probing)
2026-08-19T08:42:28.724Z mirb: localhost:8080 <- localhost:80 ready
2026-08-19T08:42:28.724Z mirb: session connecting (2 ready)
2026-08-19T08:42:28.724Z mirb: session ready (2 ready)mirb logs -f api follows. mirb logs -n 200 api shows more history.
$ mirb stop lxa2xk lxa2xk 10.0.0.7 stoppedAny unique id prefix works, as does the --name label or the host. mirb stop --all ends
everything. More in Background sessions.
Name it once, use it forever
Put a target and its ports in ~/.config/mirb/config.toml:
[profiles.api]
host = "deploy@10.0.0.7"
ports = [3000, "8080:80"]$ mirb api
$ mirb api 9229 # the profile, plus a debugger portFlags still override, and extra ports append. See Profiles.
Two things worth knowing early
The port you want is already taken
mirb: error: localhost:3000 is already in use by Python (pid 37380)
mirb: hint: Pass --auto-port to take the next free port, or choose another local port.mirrorball tries to bind every local port itself before spawning ssh, which is why this
arrives instantly and names the process instead of arriving as an opaque ssh error after you
have authenticated. --auto-port walks upward to the next free port — 3000 busy means
3001, predictably, rather than a random high port.
It switches to JSON on its own
When stdout is not a terminal, mirrorball assumes a program is reading it:
$ mirb ls | jq '.data.sessions[].status'For a live tunnel, --json streams NDJSON — one event per line, written the moment it
happens:
$ mirb --json 10.0.0.7 3000{"event":"session.start","ts":"2026-08-19T08:44:09.882Z","id":"mb_hnjvhl5gzqfhc","target":{"host":"10.0.0.7","raw":"10.0.0.7"},"forwards":[{"localPort":3000,"bindAddress":"127.0.0.1","remoteHost":"localhost","remotePort":3000,"source":"3000"}]}
{"event":"forward.bound","ts":"2026-08-19T08:44:09.939Z","localPort":3000}
{"event":"forward.ready","ts":"2026-08-19T08:44:10.692Z","localPort":3000}
{"event":"session.ready","ts":"2026-08-19T08:44:10.693Z","id":"mb_hnjvhl5gzqfhc","ready":1,"total":1}Progress and errors always go to stderr, so redirecting stdout leaves you a clean file and still shows you what is happening.
Next
- Concepts — target, forward, session, profile, and the readiness model in full.
- Port syntax — every accepted form, and every rejected one.
- Background sessions — supervision, reconnects, and where the state lives.
- How it works — the exact ssh command behind all of this.
Last updated on
Installation
Every way to install mirrorball — the shell installer, a release archive, or from source — plus verifying, upgrading, uninstalling, and where files land.
Concepts
The four nouns mirrorball uses — target, forward, session, profile — and the three readiness states that make it different from ssh -L.