@wishtunnel/server
v1.0.13
Published
Wishtunnel server: Fastify reverse proxy + WebSocket tunnel gateway.
Maintainers
Readme
@wishtunnel/server
The wishtunnel backend: a Fastify reverse proxy that routes wildcard-subdomain HTTP traffic into multiplexed CLI WebSocket tunnels.
Running
npm install -g @wishtunnel/server # or use deploy/Dockerfile.server
DOMAIN=wishtunnel.com JWT_SECRET=... wishtunnel-server
# or from the monorepo:
pnpm --filter @wishtunnel/server build && node apps/server/dist/index.jsEnvironment variables
| Variable | Default | Description |
| --- | --- | --- |
| PORT | 8080 | HTTP + WebSocket listen port |
| HOST | 0.0.0.0 | Bind address |
| DOMAIN | wishtunnel.local | Apex domain; tunnels live at <sub>.$DOMAIN |
| PUBLIC_SCHEME | https | Scheme used in public tunnel URLs (TLS terminates at nginx) |
| JWT_SECRET | (unset) | If set, clients must AUTH with a valid HS256 JWT; unset = anonymous tunnels |
| JWT_ISSUER | (unset) | If set, the token's iss claim must match exactly |
| JWT_AUDIENCE | (unset) | If set, the token's aud claim must match (or include) this value |
| JWT_CLOCK_TOLERANCE_SEC | 30 | Allowed clock skew when validating exp / nbf |
| JWT_REQUIRE_EXP | true | Reject tokens without an exp claim (tokens must be time-limited) |
| JWT_MAX_LIFETIME_SEC | (unset) | If set, reject tokens whose exp - iat exceeds it, e.g. 21600 for 6h |
| MAX_BODY_BYTES | 67108864 | Per-request body cap (64 MiB) |
| RATE_LIMIT_MAX | 100 | Requests per window per tunnel |
| RATE_LIMIT_WINDOW_MS | 1000 | Rate limit window |
| HEARTBEAT_INTERVAL_MS | 30000 | WS ping interval; sockets missing 2 pongs are terminated |
| REQUEST_TIMEOUT_MS | 30000 | Max wait for the CLI to produce response headers (then 504) |
| STREAM_IDLE_TIMEOUT_MS | 120000 | Idle multiplexed streams are destroyed after this |
| RECONNECT_GRACE_MS | 60000 | Subdomain reservation window for reconnecting CLIs |
| SHUTDOWN_GRACE_MS | 15000 | Graceful-shutdown drain window for active streams |
| LOG_LEVEL | info | pino log level |
Endpoints
GET https://$DOMAIN/healthz— platform health + tunnel countswss://$DOMAIN/_tunnel— CLI tunnel gateway*.$DOMAIN/**— proxied into the matching tunnel
Token lifetime
Tokens issued by the Telegram bot are short-lived (~6h). The server rejects
tokens with no exp claim and tears a session down when its token lapses
mid-connection: the CLI gets REGISTER_ERROR pointing at the bot and exits
instead of holding a tunnel open on stale credentials.
Graceful shutdown
On SIGTERM/SIGINT the server: stops accepting tunnels, answers new HTTP
requests with 503, drains in-flight streams (up to SHUTDOWN_GRACE_MS),
sends DISCONNECT to every CLI, then closes Fastify.
Deployment
Use deploy/Dockerfile.server + deploy/docker-compose.yml (nginx terminates
TLS with a wildcard certificate and upgrades /_tunnel WebSockets). A
wildcard DNS record (*.your-domain.com) must point at the edge.
