Pranor Tunnel

pranor-tunnel client --port 3000 --server tunnel.pranor.net
# → Exposes local port 3000 at https://abc123.tunnel.pranor.net
docker run -p 8092:8092 ghcr.io/vyuvaraj/pranor-tunnel:latest

Pranor Tunnel is a secure, instant tunneling service for exposing local Pranor services to the internet during development and testing. One command creates a public URL that forwards requests to your local machine — ideal for webhook testing, OAuth callbacks, mobile app dev, and sharing work in progress.


Table of Contents


Key Features

🌐 Core Tunneling

  • Subdomain-based routing: Each tunnel gets a unique subdomain (e.g., myapp.pranor.net)
  • WebSocket transport: Firewall-friendly tunneling over WebSocket — no special network configuration required
  • WebSocket connection multiplexing: Binary-framed multiplexed streams (4-byte StreamID + 1-byte Type + 4-byte PayloadLen) allow multiple simultaneous requests over a single WebSocket connection
  • OTel traceparent propagation: traceparent and tracestate headers forwarded natively through the tunnel for distributed tracing continuity

🔍 Request Inspection & Replay

  • Full request & response body capture: Ring-buffer captures all requests and responses for debugging
  • Replay-on-demand: Replay any captured request to your local service with one API call
  • Real-time request log: Colorful terminal output with status codes, latency, and method — like a local dev proxy

🔒 Authentication & Access Control

  • JWT auth gating: Require a valid JWT token to open a tunnel connection — prevents unauthorized forwarding
  • API-key auth: Alternative to JWT; pass a static API key in the Authorization header
  • Shareable tunnel URLs with expiry: Generate a time-limited shareable URL (e.g., valid for 1h) — auto-expires after
  • One-time access tokens: Single-use tunnel URLs that invalidate after first use

🔄 Resilience & Reconnection

  • Persistent reconnect with exponential backoff: Client auto-reconnects on disconnect; configurable max retries, initial delay, max delay, and jitter multiplier
  • Connection state recovery: In-flight requests are retried on reconnect within configurable grace window
  • Health & readiness probes: Standard /healthz and /readyz endpoints for container orchestration

Architecture

Browser / Webhook Sender
         │ HTTPS request to myapp.pranor.net
         ▼
┌─────────────────────────┐
│      Pranor Tunnel Server   │
│                         │
│  Subdomain Router        │
│    myapp → Conn#1        │
│  WS Multiplexer          │
│    (StreamID framing)    │
└──────────┬──────────────┘
           │ WebSocket (multiplexed)
           ▼
Pranor Tunnel Client (local machine)
           │
           ▼
Local Service (http://localhost:3000)

API Endpoints

MethodPathDescription
POST/api/v1/tunnelsCreate a new tunnel
GET/api/v1/tunnelsList active tunnels
DELETE/api/v1/tunnels/{id}Close a tunnel
GET/api/v1/tunnels/{id}/requestsBrowse captured requests (ring buffer)
POST/api/v1/tunnels/{id}/replay/{reqID}Replay a captured request
POST/api/v1/tunnels/{id}/shareGenerate a shareable URL with expiry
GET/healthzLiveness probe
GET/readyzReadiness probe

Getting Started

Server (self-hosted)

docker run -p 8092:8092 \
  -e PRANOR_TUNNEL_DOMAIN=pranor.net \
  -e PRANOR_TUNNEL_JWT_SECRET=my-secret \
  -e PRANOR_TUNNEL_OTEL_ENDPOINT=http://pranor-trace:4318 \
  ghcr.io/vyuvaraj/pranor-tunnel:latest

Client (local machine)

# Install client
go install github.com/vyuvaraj/pranor/Pranor Tunnel/cmd/pranor-tunnel@latest

# Expose local port 3000 to a public URL
pranor-tunnel --server wss://tunnel.pranor.net --local http://localhost:3000

# Output:
# ✓ Tunnel active: https://abc123.pranor.net
# Forwarding: https://abc123.pranor.net → http://localhost:3000
# Press Ctrl+C to close tunnel

Request Inspection & Replay

All requests are captured in a ring buffer:

# View captured requests
curl http://localhost:8092/api/v1/tunnels/tun-abc/requests

# Replay a specific captured request
curl -X POST http://localhost:8092/api/v1/tunnels/tun-abc/replay/req-001

The terminal client shows real-time request logs:

[2026-07-26 11:42:00] POST /webhook/payment    200  43ms
[2026-07-26 11:42:01] GET  /api/orders/123     200  12ms
[2026-07-26 11:42:03] POST /webhook/payment    500  89ms  ← error highlighted

Authentication & Access Control

# Create a tunnel with JWT auth requirement
pranor-tunnel --server wss://tunnel.pranor.net \
  --local http://localhost:3000 \
  --auth jwt \
  --jwt-token eyJhbGciOi...

# Generate a shareable URL (expires in 1 hour)
curl -X POST http://localhost:8092/api/v1/tunnels/tun-abc/share \
  -d '{"expires_in": "1h", "one_time": false}'
# → { "url": "https://abc123.pranor.net?token=xyz789", "expires_at": "..." }

Resilience & Reconnection

Configure reconnect behavior in the client:

pranor-tunnel \
  --server wss://tunnel.pranor.net \
  --local http://localhost:3000 \
  --reconnect-max-retries 10 \
  --reconnect-initial-delay 500ms \
  --reconnect-max-delay 30s \
  --reconnect-jitter 0.2

Configuration

Server Environment Variables

VariableDefaultDescription
PRANOR_TUNNEL_PORT8092HTTP/WebSocket listener port
PRANOR_TUNNEL_DOMAINBase domain for subdomains (e.g. pranor.net)
PRANOR_TUNNEL_JWT_SECRETJWT signing secret for auth gating
PRANOR_TUNNEL_MAX_RING_BUFFER100Max captured requests per tunnel
PRANOR_TUNNEL_OTEL_ENDPOINTOpenTelemetry collector URL
PRANOR_TUNNEL_TLS_CERTTLS certificate path
PRANOR_TUNNEL_TLS_KEYTLS key path

Wildcard DNS

Configure your DNS provider with a wildcard A/CNAME record pointing *.pranor.net to the Pranor Tunnel server IP.