Macha docs
Macha exposes a port on your local machine to the public internet. No account, no signup, one command.
Installation
Pick the method that suits your platform. All paths end up with a single macha binary.
Mac
curl -fsSL https://macha.live/install.sh | bash
Downloads the correct pre-built binary for Apple Silicon (aarch64) or Intel (x86_64) and installs it to /usr/local/bin.
Linux
curl -fsSL https://macha.live/install.sh | bash
Statically linked with musl — works on any Linux distribution with no libc dependency. Supports x86_64 and aarch64.
Windows
irm https://macha.live/install.ps1 | iex
Installs to %LOCALAPPDATA%\macha\bin and adds it to your user PATH. Restart your terminal after install.
From source (Cargo)
cargo install macha
Requires the Rust toolchain. Compiles from crates.io — takes about a minute on first run.
Verify
macha --version
Your first tunnel
Two things are needed: a local server running, and the subdomain you want.
-
1Start your app
Macha works with any server. The only thing it needs is a port number.
examples# Node / Express on :3000 node server.js # Python / FastAPI on :8000 uvicorn main:app --port 8000 # Anything — just know the port
-
2Run Macha
macha --port 3000 --subdomain myapp
Replace
3000with your app's port andmyappwith any subdomain you want (letters, digits, hyphens — max 63 characters). -
3Get your URL
Dashboard → http://127.0.0.1:4040 ✓ Tunnel: https://myapp.macha.live
Share
https://myapp.macha.livewith anyone. Openhttp://127.0.0.1:4040to see requests in real time.
myapp is already registered by another agent, pick a different one.
Subdomains are released when the agent disconnects.
Local dashboard
Every time you run macha, a dashboard starts automatically at
http://127.0.0.1:4040. Open it in your browser.
The dashboard shows:
- Connection status and tunnel URL
- Stats — total requests, bytes in/out, average response time, uptime
- Live request log — method, path, size, duration — updates in real time
It uses Server-Sent Events so the log updates live without polling.
Change the dashboard port
macha --port 3000 --subdomain myapp --dashboard-port 5050
Set --dashboard-port 0 to disable the dashboard entirely.
CLI flags & environment variables
Every flag has a corresponding environment variable. Flags take precedence.
| Flag | Env var | Description |
|---|---|---|
| --port, -p | PORT | Local port to expose (required) |
| --subdomain, -s | SUBDOMAIN | Subdomain to register — myapp → myapp.macha.live (required) |
| --server | MACHA_SERVER | Tunnel server hostname (default: macha.live) |
| --control-port | CONTROL_PORT | Server control plane port (default: 9000) |
| --data-port | DATA_PORT | Server data plane port (default: 9001) |
| --token | MACHA_TOKEN | Auth token (required when server has AUTH_TOKEN set) |
| --tls | — | Enable TLS using Mozilla root certs (for macha.live) |
| --tls-ca <PATH> | — | Enable TLS with a custom CA certificate (for self-signed certs) |
| --tls-insecure | — | Enable TLS without cert verification — dev only |
| --no-reconnect | — | Exit on disconnect instead of reconnecting |
| --dashboard-port | MACHA_DASHBOARD_PORT | Dashboard HTTP port (default: 4040, set 0 to disable) |
Examples
# Basic macha --port 3000 --subdomain myapp # Using env vars (good for .env files) PORT=3000 SUBDOMAIN=myapp macha # Self-hosted server with auth token macha --port 3000 --subdomain myapp \ --server tunnel.yourcompany.com \ --token my-secret-token \ --tls # Run once (no reconnect), custom dashboard port macha --port 8080 --subdomain staging \ --no-reconnect \ --dashboard-port 5050
Rust library — quick start
Embed tunneling directly in your Rust app with no external process.
[dependencies] macha = "0.2" tokio = { version = "1", features = ["full"] }
use macha; #[tokio::main] async fn main() -> macha::Result<()> { // blocks until disconnected; auto-reconnects by default macha::start("myapp", 3000).await }
Run in the background alongside your server
use macha::Tunnel; #[tokio::main] async fn main() -> macha::Result<()> { let tunnel = Tunnel::builder("myapp", 3000) .build()?; tokio::spawn(async move { let _ = tunnel.run().await; }); // start your own server here… std::future::pending::<()>().await; Ok(()) }
Builder API
All options are chainable on Tunnel::builder(subdomain, port).
| Method | Type | Description |
|---|---|---|
| .server(host) | &str | Tunnel server hostname (default: "macha.live") |
| .control_port(n) | u16 | Control plane port (default: 9000) |
| .data_port(n) | u16 | Data plane port (default: 9001) |
| .token(t) | &str | Auth token to send on REGISTER |
| .reconnect(bool) | bool | Auto-reconnect on session end (default: true) |
| .tls() | — | Enable TLS with Mozilla root certs |
| .tls_custom_ca(path) | PathBuf | Enable TLS with a custom CA cert file |
| .tls_insecure() | — | Enable TLS, skip cert verification (dev only) |
| .log_channel(tx) | broadcast::Sender<RequestLog> | Receive a RequestLog after each completed request |
| .build() | Result<Tunnel> | Validates subdomain, returns Tunnel |
use macha::Tunnel; let tunnel = Tunnel::builder("myapp", 3000) .server("tunnel.yourcompany.com") .token("my-secret-token") .tls() .reconnect(true) .build()?; tunnel.run().await?;
Log channel
Wire a tokio::sync::broadcast channel into the tunnel to receive a
RequestLog after every completed request.
use macha::{Tunnel, RequestLog}; use tokio::sync::broadcast; let (tx, mut rx) = broadcast::channel::<RequestLog>(256); // spawn log consumer tokio::spawn(async move { while let Ok(log) = rx.recv().await { println!("{} {} — {}ms", log.method, log.path, log.duration_ms); } }); Tunnel::builder("myapp", 3000) .log_channel(tx) .build()? .run().await?;
RequestLog fields
| Field | Type | Description |
|---|---|---|
| subdomain | String | The registered subdomain |
| method | String | HTTP method (GET, POST, …) |
| path | String | Request path (/api/users, …) |
| bytes_in | u64 | Bytes received from visitor (request) |
| bytes_out | u64 | Bytes sent to visitor (response) |
| duration_ms | u64 | Total time from first byte to connection close |
| timestamp_ms | u128 | Unix epoch milliseconds when request completed |
Self-hosting — Docker
Run your own tunnel server on any VPS. Your users connect to it instead of
macha.live — traffic never leaves your infrastructure.
*.tunnel.yourcompany.com → your-vps-ip.
services:
macha:
image: ghcr.io/dhineshk/macha-server:latest
restart: unless-stopped
ports:
- "80:8080" # public HTTP (put Caddy in front for HTTPS)
- "9000:9000" # agent control plane
- "9001:9001" # agent data plane
environment:
DOMAIN: tunnel.yourcompany.com
AUTH_TOKEN: change-me-to-a-random-secret
docker compose up -d
That's it — the server is running. Now point agents at it:
macha --port 3000 --subdomain myapp \
--server tunnel.yourcompany.com \
--token your-secret
Tunnel URLs will read https://myapp.tunnel.yourcompany.com.
Server environment variables
| Variable | Default | Description |
|---|---|---|
| DOMAIN | macha.live | Base domain used in tunnel URLs and Host-header parsing |
| PUBLIC_SCHEME | https | https or http — prefix in the URL returned to agents |
| AUTH_TOKEN | (none) | If set, agents must pass this token in REGISTER or be rejected |
| PUBLIC_PORT | 8080 | Port the public HTTP listener binds on inside the container |
| CONTROL_PORT | 9000 | Port agents use to register (control plane) |
| DATA_PORT | 9001 | Port agents use for idle pool connections (data plane) |
| TLS_CERT | (none) | Path to PEM certificate — enables TLS on ports 9000/9001 |
| TLS_KEY | (none) | Path to PEM private key — required with TLS_CERT |
| RUST_LOG | (none) | Log verbosity — e.g. server=info or server=debug |
HTTPS with Caddy
Caddy handles wildcard TLS certificates automatically using Let's Encrypt.
Add this alongside your docker-compose.yml:
*.tunnel.yourcompany.com {
tls {
dns <your-dns-provider>
}
reverse_proxy macha:8080
}
services:
caddy:
image: caddy:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
environment:
<DNS_PROVIDER_API_TOKEN_VAR>: <your-token>
macha:
image: ghcr.io/dhineshk/macha-server:latest
restart: unless-stopped
expose: ["8080"]
ports:
- "9000:9000"
- "9001:9001"
environment:
DOMAIN: tunnel.yourcompany.com
AUTH_TOKEN: change-me
volumes:
caddy_data:
TLS for agent connections
By default ports 9000 and 9001 are plain TCP. If you want the agent-to-server connection encrypted (important if AUTH_TOKEN is set), add TLS:
environment: TLS_CERT: /certs/fullchain.pem TLS_KEY: /certs/privkey.pem volumes: - /etc/letsencrypt/live/tunnel.yourcompany.com:/certs:ro
Then agents must connect with --tls:
macha --port 3000 --subdomain myapp \
--server tunnel.yourcompany.com \
--token my-secret \
--tls
For self-signed certificates, use --tls-ca /path/to/ca.pem instead of --tls.
Auth tokens
Set AUTH_TOKEN on the server to require all agents to authenticate.
Without a valid token, agents get ERR invalid or missing token and are disconnected.
AUTH_TOKEN=a-long-random-string-here
# flag macha --port 3000 --subdomain myapp --token a-long-random-string-here # env var MACHA_TOKEN=a-long-random-string-here macha --port 3000 --subdomain myapp
AUTH_TOKEN with TLS on ports 9000/9001 in production.
Rate limiting
The server applies two independent rate limits automatically — no configuration needed.
Per-subdomain request limit
Each subdomain has a token bucket: capacity 200, refill rate 100/second.
Requests that exceed this are rejected with HTTP 429 Too Many Requests
without hitting the agent pool. This protects against a single tunnel overwhelming the server.
Per-IP registration limit
Each unique IP address can register at most 5 subdomains per minute.
Attempts beyond this are rejected with ERR too many registration attempts.
This prevents automated subdomain squatting.
Reserved subdomains
The following subdomains cannot be registered — they are permanently blocked:
api, www, admin, root, mail, server, dashboard, status, health,
macha, app, staging, prod, beta, static, assets, cdn, auth,
login, signup, register, support, help, docs, blog, dev, test
Any attempt to register one of these returns ERR reserved subdomain.