public urls for anything

browser relay cannot decrypt encrypted tls encrypted tls your machine tls terminates here opencode localhost:47365 api localhost:3000 webhooks localhost:8080

a cli and sdk to create end-to-end encrypted public urls for apps running on your machine reachable from anywhere in the world

github
$ npm i -g @opentunnel/cli
$ bun add -g @opentunnel/cli
$ pnpm add -g @opentunnel/cli

cli

$ opentunnel create
created f7a2mx4kq9vn.opentunnel.xyz

$ opentunnel route add opencode localhost:47365
added route opencode.f7a2mx4kq9vn.opentunnel.xyz -> localhost:47365

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

sdk

import { create } from "@opentunnel/client"

const client = create()

await client.route.add({
  name: "opencode",
  target: "localhost:47365",
})

const connection = await client.tunnel.connect()

console.log(connection.routes[0].hostname)
opencode.f7a2mx4kq9vn.opentunnel.xyz

how it works

  1. opentunnel create reserves your hostname and generates a private key on your machine. the key never leaves it.

  2. the cli sends a certificate request for that hostname. a certificate is issued and bound to your tunnel name. the relay only ever sees the public half.

  3. opentunnel connect opens an encrypted bridge from your machine to the relay.

  4. visitors hit your public url. the relay reads only the hostname from the tls handshake and forwards the encrypted stream through the bridge.

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

privacy

the relay can't read your traffic
connections are routed by the hostname in the tls handshake. the bytes stay encrypted until they reach your machine. the relay has no key to decrypt them.
your tunnel hostname is public
anyone with your url can reach your services. when a tunnel is created, its certificate is published to certificate transparency logs, 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 any log. they are still guessable, especially common names like api or postgres, so don't treat them as authentication.
put auth in the services themselves
anything sensitive behind a tunnel should authenticate on its own.