A lightweight reverse tunnel system built with Bun:
server: runs on your VPS and receives public HTTP traffic.client: runs on your local machine, connects over WebSocket, and proxies requests to your local app.
This project supports:
- Subdomain-based routing (
myapp.yourdomain.com) - Request/response correlation with request IDs
- User registration and token issuance
- User roles (
admin,client) with defaultclientregistration role - Token auth and subdomain ownership for tunnel clients
- Custom HTML 404 page for unknown or offline tunnels
- Client reconnect with backoff
- Binary-safe payload forwarding via Base64
- Bun v1.1+
- A domain with wildcard DNS pointing to your VPS (
*.yourdomain.com)
server/server.ts: Bun server + WebSocket control channelclient/client.ts: tunnel agent that runs near your local appshared/protocol.ts: message protocol + validation helperspackage.json: scripts for dev and binary builds
PORT(default:8080)DOMAIN(default:example.com)DB_PATH(default:./data/tunnel.db)REQUEST_TIMEOUT_MS(default:30000)INITIAL_ADMIN_USERNAME+INITIAL_ADMIN_PASSWORD(optional bootstrap admin at startup)
- Defaults are loaded from
.env: TUNNEL_SERVER_URL(default:ws://127.0.0.1:8080/_tunnel_connect)TUNNEL_API_URL(default: derived fromTUNNEL_SERVER_URL, e.g.http://127.0.0.1:8080)TUNNEL_SUBDOMAIN(optional; if omitted, client auto-generates one)TUNNEL_LOCAL_URL(default:http://127.0.0.1:3000)TUNNEL_AUTH_TOKEN(required for tunnel mode)TUNNEL_SESSION_FILE(optional path for saved login token)TUNNEL_USERNAMEandTUNNEL_PASSWORD(for register/login modes)TUNNEL_REVOKE_TOKEN(optional fallback for revoke mode)--port,--p, or-pcan be used instead of--local(example:--port 3000->http://127.0.0.1:3000)- CLI flags still override env values when provided (
--server,--api,--subdomain,--local,--port,--p,-p,--token,--username,--password,--revoke-token).
Token persistence:
- After
--registeror--login, client auto-saves token locally and reuses it by default. - You can still override with
--token. - Use
--no-save-tokento disable saving.
bun installCreate your local env file:
# PowerShell (Windows)
Copy-Item .env.example .envbun run dev:serverSee all options:
bun run dev:client -- --helpRegister user and get first token:
bun run dev:client -- --register --username alice --password "yourStrongPass123"Note: self-registered users are created with client role by default.
Login and issue a new token:
bun run dev:client -- --login --username alice --password "yourStrongPass123"List tokens for your account:
bun run dev:client -- --list-tokens --token YOUR_ACTIVE_TOKENRevoke a token:
bun run dev:client -- --revoke-token TOKEN_TO_REVOKE --token YOUR_ACTIVE_TOKENStart tunnel with a valid token:
bun run dev:client -- --token YOUR_ISSUED_TOKENStart tunnel using auto-saved token (no --token needed):
bun run dev:clientStart tunnel by local port shortcut:
bun run dev:client -- --token YOUR_ISSUED_TOKEN --port 3000Custom subdomain:
bun run dev:client -- --token YOUR_ISSUED_TOKEN --subdomain myappOverride server/API when needed:
bun run dev:client -- --token YOUR_ISSUED_TOKEN --server wss://tunnel.yourdomain.com/_tunnel_connect --api https://tunnel.yourdomain.comNow requests sent to host <subdomain>.example.com (or with local host header override) are forwarded to your local app.
# Windows x64
bun run build:client:win
# Linux x64
bun run build:client:linux
# macOS arm64
bun run build:client:macBinaries are written into dist/.
- Put Caddy or Nginx in front for TLS (
wss://andhttps://). - Keep SQLite DB (
DB_PATH) backed up. - Consider adding rate limiting and per-user tunnel ownership.
- Add persistent tunnel/session state if you need HA across multiple server instances.
PM2 config file:
- ecosystem.config.cjs
Install PM2 on the VPS if needed:
npm install -g pm2Stop any foreground Bun server first, then start with PM2:
cd ~/test/tunnel-server
pm2 start ecosystem.config.cjs
pm2 status
pm2 logs tunnel-serverPersist across reboot:
pm2 save
pm2 startupCommon commands:
pm2 restart tunnel-server
pm2 stop tunnel-server
pm2 delete tunnel-server- Run a local app on
http://127.0.0.1:3000. - Start server and client.
- Send a request with host header:
curl -H "Host: myapp.example.com" http://127.0.0.1:8080/You should receive your local app response through the tunnel.
See deployment docs:
- docs/NGINX_DOMAIN.md
- docs/NGINX_SUBDOMAIN.md
- docs/NGINX_CERTBOT_RECOVERY.md
- docs/WILDCARD_TLS.md
- docs/APACHE_DOMAIN.md
- docs/APACHE_SUBDOMAIN.md
- docs/DEPLOYMENT.md (index)
Use the automation script to create isolated Nginx tunnel vhosts safely:
sudo bash scripts/configure-nginx-tunnel.sh --domain faiezwaseem.siteOptional TLS for control-plane host:
sudo bash scripts/configure-nginx-tunnel.sh --domain faiezwaseem.site --certbotThe script uses certbot certonly --nginx and manages TLS in the tunnel config itself (it does not let Certbot install into default.conf).
Script path:
For a single "run-and-go" command (install + fix + configure + TLS):
sudo bash scripts/bootstrap-tunnel-nginx.sh --domain faiezwaseem.siteScript path: