Secure remote access to any machine — shell, files, and web apps — from a browser or another terminal. No SSH, no port forwarding, no account, and nothing to install on the far end.
bitbang is a single Go binary. Run bitbang serve on a machine — a Raspberry Pi, a home server, a workstation behind NAT — and it prints one URL. Open that URL in any browser, or bitbang connect to it from another machine, and you get a terminal, a file browser, and a gateway to that machine's network. The connection is peer-to-peer and end-to-end encrypted; the bitba.ng signaling server only introduces the two ends, then steps aside.
Part of the BitBang project.
- Nothing to forward or configure. Works from behind NAT, CGNAT, or a locked-down network — no router changes, no VPN, no tunnel daemon.
- Nothing to install on the client. A browser is enough. A CLI is there when you want scripting, pipes, and file copy.
- Private by design. Traffic is WebRTC/DTLS, peer-to-peer. The signaling server never sees it; if a direct path isn't possible, a TURN relay carries ciphertext only.
- No account, no telemetry.
Download the binary for your platform from Releases, or build it (see Building):
go build ./cmd/bitbang/ # produces ./bitbangEvery serve command prints a URL like https://bitba.ng/<id>#<code>. Open it in a browser, or hand it to bitbang connect / bitbang cp.
bitbang serve shellA full terminal in the browser (xterm.js — colors, resize, copy/paste), or from another machine:
bitbang connect https://bitba.ng/<id>#<code> # interactive shell
bitbang connect https://bitba.ng/<id>#<code> -- tail -f /var/log/syslog # one-shot commandbitbang serve files ~/share # share a directory (read-only)
bitbang serve files ~/share -files-upload # ...and allow uploadsBrowse, preview, download, and upload in the browser — or copy from the CLI, scp-style:
bitbang cp https://bitba.ng/<id>#<code>:/var/log/app.log ./app.log # remote -> local
bitbang cp ./firmware.bin https://bitba.ng/<id>#<code>:/tmp/firmware.bin # local -> remoteReach any HTTP / WebSocket service on the machine's network — a NAS, Jellyfin, Node-RED, a Flask dashboard — through your browser:
bitbang serve proxy # choose the target in the URL
bitbang serve proxy -target localhost:8080 # or pin a single targetOpen the URL, type a LAN address (nas.local, 192.168.1.10:8080, localhost:3000/admin), and you're in. Logins, cookies, uploads, downloads, and server-sent events all work — sessions are handled by a service worker, so apps behave normally.
bitbang serve # shell + files + proxy on one URL; the browser offers eachSame URL, two front ends:
- Browser — open
https://bitba.ng/<id>#<code>. Nothing to install. - CLI —
bitbang connect <url>for a shell (add-- cmdfor one-shot),bitbang cpfor files.
The access code lives in the URL fragment (#…), which browsers never send to a server — so bitba.ng brokers the connection without ever seeing the secret that authorizes it.
- Self-certifying identity. On first run,
bitbanggenerates an RSA keypair under~/.bitbang/<program>/; the device UID is derived from the public key, so it can't be impersonated. - Optional PIN. Add
--pinfor permanent or headless setups — connectors must supply it. - End-to-end encryption. All traffic rides WebRTC's DTLS. The signaling server sees only the public key, the derived UID, and connection metadata — never your data. A TURN relay, if one is needed, sees ciphertext only.
- Throwaway mode.
-ephemeraluses a temporary identity (a fresh URL each run).
browser / bitbang connect bitba.ng bitbang serve
(client) ── handshake ─ (signaling) ─ handshake ── (device)
└────────────── encrypted WebRTC data channel ──────────────┘
(direct peer-to-peer, or TURN-relayed)
bitba.ng brokers the WebRTC handshake and then gets out of the way — application traffic flows directly between the two peers over an encrypted data channel. Shell, file, and proxy traffic are multiplexed over that single channel using SWSP (Simple WebRTC Streaming Protocol): a small streamId | flags | length | payload framing, carrying HTTP requests for file/proxy operations and long-lived streams for the shell.
| ngrok | Cloudflare Tunnel | Tailscale | bitbang | |
|---|---|---|---|---|
| Account required | Yes | Yes | Yes | No |
| Client install | No | No | Yes | No (browser) |
| Port forwarding / router config | No | No | No | No |
| Data path | Their servers | Their servers | P2P | P2P |
| Configuration | CLI flags | Config + DNS | Dashboard | None |
Flags accept either form (-pin or --pin). Boolean flags default off unless noted.
bitbang serve [flags] All caps: shell + files + proxy on one URL
bitbang serve shell [flags] Shell only
bitbang serve files [PATH] [flags] Files only (PATH defaults to cwd)
bitbang serve proxy [flags] HTTP/WebSocket reverse proxy only
bitbang connect <target> [-- cmd …] Client shell (interactive or one-shot)
bitbang cp <src> <dst> Copy files (one side is <URL>:/path, or '-')
bitbang version Print version (also --version)
bitbang help Usage (also --help, -h)
Shared flags (all four serve forms):
| Flag | Default | Description |
|---|---|---|
-server HOST |
bitba.ng |
Signaling server hostname |
-pin PIN |
(none) | Require this PIN for connections |
-ephemeral |
off | Temporary identity (a fresh URL each run) |
-nocode |
off | Disable code-exchange pairing — no 6-digit code is issued; the URL still works. Use for headless/non-TTY listeners that can't complete the SAS prompt. |
-program NAME |
bitbang |
Identity name; keypair stored at ~/.bitbang/<NAME>/identity.pem |
-target HOST:PORT |
(dynamic) | Fixed proxy target (proxy mode); empty = pick the target in the browser |
-v |
off | Verbose logging (adds the browser ?debug overlay) |
Shell flags (serve and serve shell):
| Flag | Default | Description |
|---|---|---|
-shell-cmd CMD |
$SHELL or /bin/sh |
Shell to spawn |
-shell-max-sessions N |
1 |
Max concurrent shell sessions (0 = unlimited) |
-shell-mirror |
on | Mirror shell output to the listener's console |
Files flags:
| Form | Path | Upload flag |
|---|---|---|
serve (all caps) |
-files PATH (default cwd) |
-files-upload |
serve files [PATH] |
positional PATH (default cwd) |
-upload |
(Advanced: -video-fd N passes an inherited socketpair FD to an external video helper; for internal/embedding use.)
<target> may be any of:
- a saved name — e.g.
nas1; resolved from the known-hosts table (see below) - a 6-digit pair code — e.g.
482731; runs the pairing flow, then connects - a URL —
https://bitba.ng/<id>#<code>,bitba.ng/<id>#<code>, or bare<id>#<code>
With no -- command, opens an interactive shell (a PTY when stdin is a terminal). With -- command args…, runs that single command non-interactively and exits with its status (signal exits report 128).
| Flag | Default | Description |
|---|---|---|
-name NAME |
(auto) | Remember this host under NAME (new hosts only; auto-assigns device<N> if omitted) |
-relay |
off | Request a TURN relay up front instead of only on fallback (ICE still prefers a direct path if one succeeds) |
-pin PIN |
(prompt) | PIN to send if the listener requires one (skips the interactive prompt) |
-timeout DUR |
30s |
Dial timeout (e.g. 45s, 1m) |
-server HOST |
bitba.ng |
Signaling server — pair-code mode only; the URL form carries its own host |
-v |
off | Verbose logging |
Exactly one of <src> / <dst> is remote, written <URL>:/path (URL in any form accepted by connect). - means stdin/stdout, so cp <URL>:/f - streams to stdout and cp - <URL>:/f uploads from stdin. A trailing / or . on the local side keeps the remote basename (scp-style).
| Flag | Default | Description |
|---|---|---|
-relay |
off | Request a TURN relay up front (as in connect) |
-pin PIN |
(prompt) | PIN to send if required |
-timeout DUR |
30s |
Dial timeout |
-v |
off | Verbose logging |
Every successful connect or pairing is remembered in ~/.bitbang/devices.json (mode 0600), so you can reconnect by a short name instead of a URL or code:
bitbang connect 482731 -name nas1 # pair once, save it as "nas1"
bitbang connect nas1 # thereafter, just the name-name NAMEchooses the name; it applies only to a new host. Without it, an auto name (device1,device2, …) is assigned and printed (Saved as "device1".).- Naming rules: a name must start with a letter and contain only letters, digits,
-, or_. That guarantees it can never be mistaken for a 6-digit code or a URL. Lookups and uniqueness are case-insensitive. - No renaming via connect:
bitbang connect nas1 -name nas2is rejected —-nameis for first-time saves only. - When it's saved: a pairing is recorded as soon as the SAS is verified (so a flaky reconnect doesn't lose it); a URL connect is recorded once connected.
- Each entry stores
{name, uid, access_code, server, paired_at}. Reconnecting a known host (by name or URL) refreshes it in place and keeps the name.
Requires Go 1.25+. Pure Go, statically linked (CGO_ENABLED=0) — trivial cross-compilation, no runtime dependencies.
go build ./cmd/bitbang/
# cross-compile:
GOOS=linux GOARCH=arm64 go build -o bitbang-arm64 ./cmd/bitbang/
GOOS=linux GOARCH=arm GOARM=7 go build -o bitbang-armv7 ./cmd/bitbang/
GOOS=windows GOARCH=amd64 go build -o bitbang.exe ./cmd/bitbang/
GOOS=darwin GOARCH=arm64 go build -o bitbang-macos ./cmd/bitbang/Shipping today: shell, files, and proxy, reachable from the browser or the CLI, plus scp-style file copy and ad-hoc pairing — short 6-digit codes with a human-verified challenge (SAS) and a saved device table (bitbang connect nas1). Designed and on the way:
- Serial bridging — drive a remote
/dev/ttyUSB0from a local virtual port (e.g. the Arduino IDE, over the internet). - TCP port forwarding —
-L 5432:db.internal:5432to reach LAN-only services. - Remote desktop — screen over a WebRTC video track, keyboard/mouse over the data channel.
- Network mode — team/fleet access with enrollment, scoped tokens, device discovery, and audit logging.
MIT — see LICENSE.
Issues and PRs welcome.