browser

*.opentunnel.xyz

opencode

localhost:47365

api

localhost:3000

webhooks

localhost:8080

a cli and sdk to create end-to-end encrypted public urls forapps running on your machine reachable from anywhere in theworld

github

$ curl -fsSL https://opentunnel.xyz/install | sh

cli

$ opentunnel route add opencode 47365
creating tunnel... tunnel is ready.
added route opencode → 127.0.0.1:47365
https://opencode.f7a2mx4kq9vn.opentunnel.xyz

$ curl https://opencode.f7a2mx4kq9vn.opentunnel.xyz
hello from localhost:47365

sdk

// bun add @opentunnel/client
import { create } from "@opentunnel/client"

const client = create()
const connection = await client.tunnel.connect({
  routes: { opencode: "127.0.0.1:47365" },
})

console.log(`https://opencode.${connection.tunnel.hostname}`)
https://opencode.f7a2mx4kq9vn.opentunnel.xyz

how it works

  1. your first route creates a tunnel: a random hostname, and aprivate key generated on your machine. the key never leaves it.

  2. the cli sends a certificate request for that hostname. acertificate is issued and bound to your tunnel name. the relayonly ever sees the public half, and renews the certificate forthe same key before it expires.

  3. a service on your machine keeps an encrypted bridge open tothe relay. apps using the sdk share the same tunnel, eachwith its own routes.

  4. visitors hit your public url. the relay reads only the hostnamefrom the tls handshake and forwards the encrypted streamthrough the bridge.

  5. your machine terminates tls with its private key and proxiesthe traffic to your local apps.

privacy

the relay can't read your traffic
connections are routed by the hostname in the tls handshake. thebytes stay encrypted until they reach your machine. the relay hasno key to decrypt them.

your tunnel hostname is public
anyone with your url can reach your services. when a tunnel iscreated, its certificate is published to certificate transparencylogs, so the hostname is discoverable by anyone watching them.

route names are private, not secret
the certificate is a wildcard, so route names never appear in anylog. they are still guessable, especially common names like api orpostgres, so don't treat them as authentication.

put auth in the services themselves
anything sensitive behind a tunnel should authenticate on its own.