Portshare is a tiny development load balancer for your Mac. Run several copies of your app behind a single localhost port — even apps that hardcode 3000 and can’t be told otherwise.
portshare run 3030 --workers 2 --intercept -- bun examples/server.ts
On Linux, several copies of an app can share one port and the kernel spreads connections between them. macOS doesn’t balance listeners that way, so Portshare does it for you: a broker owns the public port, each worker gets a private one, and every new TCP connection goes to the next ready worker.
A small Rust broker listens on 127.0.0.1:PORT and a private control socket.
portshare ready on 127.0.0.1:3030 control /tmp/portshare-you-3030.sock
Each copy gets its own loopback port — via PORT, a {port} placeholder, or native interception of a hardcoded bind().
worker 1 ready: pid 48211, 3030 -> 127.0.0.1:51842 worker 2 ready: pid 48212, 3030 -> 127.0.0.1:55107
Each new TCP connection goes to the next worker. Keep-alive, WebSockets and SSE stay put for their lifetime.
$ curl -s :3030 | jq .message "Hello from 1!" $ curl -s :3030 | jq .message "Hello from 2!"
A faithful simulation of the real CLI, running entirely in your browser. Type commands into either terminal, or use the shortcuts. Try breaking it, too: drop --intercept against the hardcoded-port example, or kill a worker.
help, cat examples/server.ts, portshare serve 3030
No config files, no daemons to install, no proxy runtime. Just one command.
--intercept loads a native helper that redirects the app’s bind() to a private port. Readiness is confirmed by an actual registered listener, not a log line.
status --watch shows each worker’s PID, private port, active connections and bytes in each direction, updating while streams stay open. --json for scripts.
Ctrl-C stops the workers and the broker that command owns. It never silently takes over an existing session or deletes a socket it didn’t create.
Apps that read PORT just work. CLI flags can use a {port} placeholder. Each worker also gets PORTSHARE_WORKER.
Bounded 64 KiB buffers per direction and independent half-close. Binary, slow readers, streaming and keep-alive are covered by the test suite.
Run portshare serve once, then attach worker groups from other terminals with --attach. Mix runtimes behind one port.
PORTNo interception needed. Each worker receives its own PORT, HOST=127.0.0.1 and PORTSHARE_WORKER.
$ portshare run 3000 --workers 4 -- bun server.ts $ portshare run 3000 --workers 2 -- \ python3 -m http.server '{port}' --bind 127.0.0.1
A macOS interposing helper rewrites the app’s listener to a private port. Every worker still believes it’s on 3030.
$ portshare run 3030 --workers 2 --intercept -- \ bun examples/server.ts # experimental · launch the runtime directly, # shell wrappers can strip DYLD_*
Portshare is a development tool, not a production proxy. It’s upfront about that.
Build from source in under a minute. Requires macOS, Rust and the Xcode command-line tools.