A self-hosted tunneling tool — like ngrok, but on your own domain. Expose local dev servers through subdomains on your VPS.
Don't want to manage a VPS? Join the waitlist for a fully managed, pre-hosted Proxly solution — no server setup required. Join the waitlist →
- A relay server runs on your VPS behind Nginx (which handles TLS)
- You run
proxlylocally — pick a project from your global config, and it opens one WebSocket per tunnel - Incoming browser requests hit
myapp.yourdomain.com→ Nginx → relay → WebSocket → your machine → local server → response back
- A VPS with a public IP
- A domain (e.g.
yourdomain.com) with wildcard DNS support - Node.js 18+ on both VPS and local machine
npm install -g @a1tem/proxlyOr from source:
# From the repo root:
npm install
cd packages/client && npm run build
npm install -g ./packages/clientSee VPS_SETUP.md for full setup instructions — Docker (recommended) and manual options, including DNS, TLS certificate issuance, Nginx config, and relay server deployment.
proxlyOn first run (no ~/.proxly.json found), you'll be guided through setup:
Welcome to Proxly!
? Is your relay server self-hosted? Yes
Let's set up your config.
? Username: alice
? Relay host (default: yourdomain.com):
? Secret token (PROXLY_SECRET from your server .env): ****
Config saved to ~/.proxly.json
Let's add your first project.
? Project name: myapp
? Tunnel name: myapp
? Port or target URL: 3000
? Add another tunnel? No
Project "myapp" added with 1 tunnel. Starting...
When you have multiple projects configured, proxly shows a picker:
$ proxly
? Which project?
> myapp — 2 tunnels
sideproject — 1 tunnel
With only one project, it starts automatically:
$ proxly
Starting project: myapp
myapp https://myapp.yourdomain.com -> http://localhost:3000
myapi https://myapi.yourdomain.com -> http://localhost:8080
Each tunnel reconnects automatically with exponential backoff if the relay is restarted. Press Ctrl+C to close all tunnels cleanly.
proxly addproxly deleteRemoves ~/.proxly.json and starts fresh:
proxly resetproxly help| Field | Type | Description |
|---|---|---|
name |
string | Subdomain to use (e.g. myapp → myapp.yourdomain.com) |
port |
number | Shorthand for http://localhost:<port> |
target |
string | Full URL — http:// or https:// (self-signed certs accepted) |
Only one of port or target is required per tunnel entry.
Browser
│ https://myapp.yourdomain.com/path
▼
Nginx (TLS termination)
│ http://127.0.0.1:3100
▼
Relay server (packages/server)
│ WebSocket: wss://yourdomain.com/_proxly/tunnel?secret=...&name=myapp
▼
proxly CLI (packages/client)
│ http://localhost:3000/path
▼
Local dev server
All messages are JSON over WebSocket.
Relay → Client:
| Type | Fields |
|---|---|
request |
id, method, path, headers, body (base64) |
ws-open |
id, path, headers |
ws-message |
id, data (base64), binary |
ws-close |
id |
ping |
— |
Client → Relay:
| Type | Fields |
|---|---|
response |
id, status, headers, body (base64) |
ws-message |
id, data (base64), binary |
ws-close |
id |
pong |
— |
# Install all dependencies
npm install
# Build both packages
cd packages/server && npm run build
cd packages/client && npm run build
# Watch mode (separate terminals)
cd packages/server && npm run dev
cd packages/client && npm run devMIT — see LICENSE
