/ docs ← macha.live

Macha docs

Macha exposes a port on your local machine to the public internet. No account, no signup, one command.

New here? Start at Your first tunnel — you'll have a live URL in under a minute.

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

PowerShell
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.

  • 1
    Start 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
  • 2
    Run Macha
    macha --port 3000 --subdomain myapp

    Replace 3000 with your app's port and myapp with any subdomain you want (letters, digits, hyphens — max 63 characters).

  • 3
    Get your URL
      Dashboard → http://127.0.0.1:4040
    ✓  Tunnel: https://myapp.macha.live

    Share https://myapp.macha.live with anyone. Open http://127.0.0.1:4040 to see requests in real time.

Subdomain taken? Subdomains are first-come, first-served per session. If 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.

FlagEnv varDescription
--port, -pPORTLocal port to expose (required)
--subdomain, -sSUBDOMAINSubdomain to register — myappmyapp.macha.live (required)
--serverMACHA_SERVERTunnel server hostname (default: macha.live)
--control-portCONTROL_PORTServer control plane port (default: 9000)
--data-portDATA_PORTServer data plane port (default: 9001)
--tokenMACHA_TOKENAuth token (required when server has AUTH_TOKEN set)
--tlsEnable TLS using Mozilla root certs (for macha.live)
--tls-ca <PATH>Enable TLS with a custom CA certificate (for self-signed certs)
--tls-insecureEnable TLS without cert verification — dev only
--no-reconnectExit on disconnect instead of reconnecting
--dashboard-portMACHA_DASHBOARD_PORTDashboard 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.

Cargo.toml
[dependencies]
macha = "0.2"
tokio = { version = "1", features = ["full"] }
src/main.rs — one-liner
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).

MethodTypeDescription
.server(host)&strTunnel server hostname (default: "macha.live")
.control_port(n)u16Control plane port (default: 9000)
.data_port(n)u16Data plane port (default: 9001)
.token(t)&strAuth token to send on REGISTER
.reconnect(bool)boolAuto-reconnect on session end (default: true)
.tls()Enable TLS with Mozilla root certs
.tls_custom_ca(path)PathBufEnable 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
full example
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

FieldTypeDescription
subdomainStringThe registered subdomain
methodStringHTTP method (GET, POST, …)
pathStringRequest path (/api/users, …)
bytes_inu64Bytes received from visitor (request)
bytes_outu64Bytes sent to visitor (response)
duration_msu64Total time from first byte to connection close
timestamp_msu128Unix 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.

Prerequisites: A VPS with Docker installed, and a domain with a wildcard DNS A record: *.tunnel.yourcompany.com → your-vps-ip.
docker-compose.yml
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

VariableDefaultDescription
DOMAINmacha.liveBase domain used in tunnel URLs and Host-header parsing
PUBLIC_SCHEMEhttpshttps 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_PORT8080Port the public HTTP listener binds on inside the container
CONTROL_PORT9000Port agents use to register (control plane)
DATA_PORT9001Port 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:

Caddyfile
*.tunnel.yourcompany.com {
    tls {
        dns <your-dns-provider>
    }
    reverse_proxy macha:8080
}
docker-compose.yml (with Caddy)
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:
Caddy supports wildcard certs via DNS challenge. Check the Caddy docs for your DNS provider's plugin (Cloudflare, Route53, etc.).

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:

docker-compose.yml additions
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.

server — environment variable
AUTH_TOKEN=a-long-random-string-here
agent — flag or env var
# 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
Security note: Tokens are sent in plain text over the control channel. Always pair 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.