Your service dials out to a hub, then serves a plain http.Handler
back through the connection it opened. Nothing listens, nothing is
published, no hole in the NAT. To the hub, the peer is just another
HTTP backend.
holt is a Go library first. The CLI is one opinionated packaging of it... made for fun.
- π³οΈ No inbound anything. The peer only dials out. NAT, locked-down containers and field devices are fine.
- π§© A library you embed. Two constructors; the hub hands you an
http.RoundTripperper peer, plus presence. - βοΈ Passes anywhere. WebSocket carrier with JWT auth: Cloudflare, ingresses and access proxies just work.
- π A hostname per peer.
checkout.example.comreaches the peer, so webhooks, OAuth callbacks and browsers work. - π¦ HTTP and gRPC through the tunnel, with optional end-to-end TLS inside it.
- π Live traffic view. Headers, payloads and timings in the console; any request replays as
curl. - πͺ€ Capture endpoints. Throwaway addresses that accept any call. Inspect a webhook without exposing a service.
- ποΈ Operable.
ls/kill/block, a web console, Prometheus metrics, a Grafana dashboard. - π Deploys small. One binary, a Docker image, a Helm chart; several hubs share one PostgreSQL.
brew install openotters/tap/holt # or: go install github.com/openotters/holt/cmd/holt@latest
holt hub --ui & # hub + console on 127.0.0.1:7201
holt expose localhost:3000 --peer web -y # enrolls itself, serves the tunnel
curl -H 'x-tunnel-peer: web' http://127.0.0.1:7202/Give peers real hostnames by fronting the hub with your TLS edge:
holt hub --advertise-addr wss://holt.example.com \
--proxy-routing both --proxy-domain example.com
holt expose localhost:3000 --peer checkout -y
# https://checkout.example.com/ now reaches the serviceThe peer attaches, serves your handler through the tunnel, and redials with backoff:
cl := holt.NewClient("wss://holt.example.com", myHandler, holt.WithBearerToken(token))
err := cl.Run(ctx)The hub is one call, and every peer becomes an ordinary HTTP backend:
srv := holt.NewServer(
holt.WithTunnel(holt.NewTunnel(":7200", holt.WithAuthBearer(peerForToken))),
holt.WithProxy(holt.NewProxy(":7202")),
)
go srv.Run(ctx)
client := &http.Client{Transport: srv.Registry().RoundTripper(peerID)}Bring your own auth, middleware, listeners and storage. Everything the CLI adds is built on this surface. See Library and How it works.
holt hub --ui. Point a Stripe webhook at a capture endpoint and read
it, signature and payload included, without exposing anything:
frp, ngrok and inlets do more, at a bigger scale: TCP/UDP, load balancing, teams, hosted service. holt is HTTP(S) and gRPC through one hub on your own infra, and a library to embed. If that is your case, everything stays yours.
β οΈ Alpha, extracted from openotters, where it is the daemon-to-agent channel. The wire protocol may still change.
| Get started | Install Β· How it works |
| Use holt | CLI Β· Web console |
| Operate | Security Β· Kubernetes Β· Observability |
| Build with holt | Library Β· Examples Β· Development |



