1npm install -g traforo
1traforo -p 3000
--, traforo auto-detects the port from
the process output so you don't need -p at all.12traforo -- pnpm dev traforo -- next start
1traforo -p 3000 -t my-app
1234traforo -- next start traforo -- pnpm dev traforo -p 5173 -- vite traforo -p 3000 -- next start # explicit port overrides auto-detection
1https://{tunnel-id}-tunnel.traforo.dev
12345678-p, --port <port> Local port to expose (optional with -- command) -t, --tunnel-id [id] Custom tunnel ID (prefer random default) -c, --cache [key] Enable edge caching (optional partition key) --password <password> Protect the tunnel with a password -h, --host [host] Local host (default: localhost) -s, --server [url] Custom tunnel server URL --help Show help --version Show version
--, traforo can detect the local port from the
process output. It watches stdout and stderr for addresses like these:1234http://localhost:3000 localhost:5173 127.0.0.1:8080 0.0.0.0:4321
-p, traforo uses that explicit port instead of
auto-detecting from process output.1traforo -p 3000 --cache
X-Traforo-Cache response header shows HIT, MISS, or BYPASS
for debugging. When BYPASS/MISS comes from the local origin path,
X-Traforo-Cache-Reason explains why.Cache-Control headers
(public, max-age, s-maxage)200/301=120m, 302/303=20m, 404/410=3m206 Partial Content responses (Cache API put() limitation)Set-Cookie, Cache-Control: no-store/no-cache/privateAuthorization, Cache-Control: no-cache/no-store/max-age=0,
or Pragma: no-cache bypass edge cache lookup.12traforo -p 3000 --cache v1 # first deployment traforo -p 3000 --cache v2 # new deploy, fresh cache
1traforo -p 3000 --password mysecret
traforo-password cookie is set and they can browse normally.4013.401 Unauthorized response with
instructions to pass the password as a cookie:1curl -b 'traforo-password=mysecret' https://{tunnel-id}-tunnel.traforo.dev
--, traforo injects TRAFORO_URL into the
child process environment with the full public tunnel URL:1TRAFORO_URL=https://{tunnel-id}-tunnel.traforo.dev
1const baseUrl = process.env.TRAFORO_URL
sh -c and reference $TRAFORO_URL.123traforo -p 3000 -- sh -c 'APP_URL=$TRAFORO_URL exec node server.js' traforo -p 3000 -- sh -c 'NEXT_PUBLIC_URL=$TRAFORO_URL exec next dev' traforo -p 3000 -- sh -c 'VITE_BASE_URL=$TRAFORO_URL exec vite'
.env / startup script and let traforo override only
TRAFORO_URL, reading it where needed:12// next.config.js const baseUrl = process.env.APP_URL || process.env.TRAFORO_URL || 'http://localhost:3000'
node_modules/.bin to PATH.
Traforo passes the full parent environment to child commands, so
project-local binaries work without pnpm exec or npx:123pnpm traforo -- vite dev pnpm traforo -- next start bun traforo -- wrangler dev
12X-Forwarded-Host: {tunnel-id}-tunnel.traforo.dev X-Forwarded-Proto: https
trust-proxy),
and Hono use these to construct correct redirect URLs and absolute links
instead of pointing back to localhost.X-Forwarded-Host or X-Forwarded-Proto, redirects
and OAuth callbacks will use the public tunnel URL automatically.traforo -- wrangler dev,
traforo sets CLOUDFLARE_INCLUDE_PROCESS_ENV=true in the child process
environment. This tells wrangler to pass parent env vars (including
TRAFORO_URL) as local development bindings, so process.env.TRAFORO_URL
works inside workerd.1traforo -- wrangler dev
12// Inside your worker: const baseUrl = process.env.TRAFORO_URL
.dev.vars file exists in the project. If you use
.dev.vars, add TRAFORO_URL there manually or use a startup script that
reads the env var.CLI Client Cloudflare Edge Local Server │ │ │ │ WebSocket connect │ │ │ ────────────────────► │ │ │ │ │ │ ┌─────┴─────┐ │ │ │ Durable │ HTTP request │ │ │ Object │ ◄─── browser/curl │ │ └─────┬─────┘ │ │ │ │ │ forward request via WS │ │ │ ◄──────────────────── │ │ │ │ │ │ http://localhost:PORT │ │ ──────────────────────────────────────────────────► │ │ │ │ │ ◄────────────────────────────────────────────────── │ │ response │ │ │ ────────────────────► │ │ │ ┌─────┴─────┐ │ │ │ respond │ ───► browser/curl │ │ └───────────┘ │
1234/traforo-status Check if tunnel is online /traforo-upstream WebSocket endpoint for local client /traforo-login POST endpoint for password login /* All other paths proxied to local server
1234567891011import { TunnelClient } from 'traforo/client' import { runTunnel } from 'traforo/run-tunnel' const client = new TunnelClient({ localPort: 3000, tunnelId: 'my-app', cacheKey: 'v1', // optional: enable edge caching password: 'mysecret', // optional: password protection }) await client.connect()
example.com)npm install -g wrangler123git clone https://github.com/remorses/traforo.git cd traforo pnpm install
worker/wrangler.json and replace the routes with your domain:12345678{ "routes": [ { "pattern": "*-tunnel.example.com/*", "zone_name": "example.com" } ] }
1234Type: CNAME Name: *-tunnel Target: example.com Proxy: Proxied (orange cloud)
{id}-tunnel.example.com subdomains through Cloudflare to your worker.1wrangler deploy -c worker/wrangler.json
TRAFORO_BASE_DOMAIN environment variable to point the CLI at your instance:1234export TRAFORO_BASE_DOMAIN=example.com traforo -p 3000 # Tunnel: https://{id}-tunnel.example.com
~/.zshrc, ~/.bashrc) to make it permanent:1echo 'export TRAFORO_BASE_DOMAIN=example.com' >> ~/.zshrc
1TRAFORO_BASE_DOMAIN=example.com traforo -- pnpm dev
baseDomain directly or rely on the env variable:12345678910import { TunnelClient } from 'traforo/client' const client = new TunnelClient({ localPort: 3000, tunnelId: 'my-app', baseDomain: 'example.com', // or set TRAFORO_BASE_DOMAIN }) await client.connect() // https://my-app-tunnel.example.com