A WebSocket relay that tunnels traffic through shared hosting — connects restricted-network clients to the free internet via a Node.js relay and xray VLESS.
Disclaimer: This project is intended for research and educational purposes only. It demonstrates WebSocket-based TCP tunneling techniques over shared hosting infrastructure. Users are responsible for ensuring their use complies with all applicable laws and the terms of service of any platforms involved. The authors assume no liability for misuse.
WebSocket relay tunnel. Your device connects directly to a shared hosting relay, which pipes traffic to an exit server that connects to the internet on your behalf.
your device (xray/Hiddify) ──VLESS+WS──► relay (shared hosting) ──WS──► outbound.js (exit server) ──TCP──► xray (exit server) ──► internet
No restricted network needed. The relay on shared hosting is the only middleman.
| File | Machine | Purpose |
|---|---|---|
relay.js |
shared hosting | WebSocket relay — bridges client and exit worker connections |
index.js |
shared hosting | Entry point — run via hosting Node.js panel |
outbound.js |
exit server | Persistent worker pool — connects relay to xray exit server |
xray-exit.json |
exit server | xray config — VLESS TCP inbound → freedom outbound |
xray-client.json |
your device | xray config — SOCKS5/HTTP proxy → VLESS+WS → relay |
.env.relay |
shared hosting | Config for relay.js |
.env.outbound |
exit server | Config for outbound.js |
xray-restricted.jsonis kept as a reference for a restricted-network setup but is not required.
git clone https://github.com/rostam-sodagari/ghostrelay.git
cd ghostrelay- shared hosting with Node.js support (cPanel, Plesk, DirectAdmin, etc.)
- exit server with Node.js installed
- xray-core installed on the exit server
- xray / Hiddify / v2rayNG on your device
Upload the project to your Node.js app directory via File Manager or Git.
npm installStart via the "Run JS Script" button in the hosting Node.js panel.
Do not use Passenger — it strips WebSocket upgrade headers.
The relay reads .env.relay:
PORT=3000
WS_PATH=/vmess
TUNNEL_SECRET=your-secret-key
PING_MS=20000
Copy outbound.js, .env.outbound, and xray-exit.json to the exit server.
npm install wsStart xray:
xray -c xray-exit.jsonStart the outbound worker pool:
# with pm2 (recommended — auto-restarts on crash)
npm install -g pm2
pm2 start outbound.js -- .env.outbound
pm2 save
pm2 startup
# or with nohup
nohup node outbound.js .env.outbound > outbound.log 2>&1 &
echo $! > outbound.pidThe outbound reads .env.outbound:
RELAY_HOST=your.relay-domain.com
RELAY_PORT=3000
RELAY_PATH=/vmess?side=exit&secret=your-secret-key
TARGET_HOST=127.0.0.1
TARGET_PORT=10800
RECONNECT_SEC=2
POOL_SIZE=16
Use xray-client.json directly:
xray -c xray-client.jsonExposes SOCKS5 on 127.0.0.1:1080 and HTTP proxy on 127.0.0.1:8080.
Import this VLESS link:
vless://your-uuid-here@your.relay-domain.com:3000?encryption=none&type=ws&path=/vmess?side=client&secret=your-secret-key#tunnel-relay
Or enter manually:
| Field | Value |
|---|---|
| Protocol | VLESS |
| Address | your.relay-domain.com |
| Port | 3000 |
| UUID | your-uuid-here |
| Encryption | none |
| Transport | WebSocket |
| WS Path | /vmess?side=client&secret=your-secret-key |
| TLS | off |
- Your device sends traffic through xray as VLESS over WebSocket to
your.relay-domain.com:3000 - The relay on shared hosting pairs the connection with a waiting exit worker from the pre-connected pool
- The exit worker forwards raw bytes to xray exit server on
127.0.0.1:10800 - xray exit server decrypts VLESS, resolves the destination using
1.1.1.1/8.8.8.8, and connects to the internet
- VLESS instead of VMess — no per-chunk AES encryption overhead
- Mux — 8 concurrent streams share one WebSocket connection
- TCP Fast Open + TCP No Delay — reduces handshake RTT, disables Nagle buffering
- Pool of 16 pre-connected exit workers — connections pair instantly, no setup delay
- Backpressure in outbound.js — pauses the faster side when the slower side's buffer fills
- DNS on exit server — domains resolved by Cloudflare/Google DNS on exit server, not locally
| Key | Default | Description |
|---|---|---|
PORT |
3000 |
Port relay listens on |
WS_PATH |
/vmess |
WebSocket path prefix |
TUNNEL_SECRET |
— | Shared secret — passed as ?secret= query param |
PING_MS |
20000 |
Keepalive ping interval in ms |
| Key | Default | Description |
|---|---|---|
RELAY_HOST |
— | relay hostname |
RELAY_PORT |
3000 |
relay port |
RELAY_PATH |
/ |
WS path including ?side=exit&secret=... |
TARGET_HOST |
127.0.0.1 |
xray exit server inbound host |
TARGET_PORT |
10800 |
xray exit server inbound port |
POOL_SIZE |
8 |
Number of pre-connected workers |
RECONNECT_SEC |
2 |
Seconds before reconnecting after a session ends |
RELAY_TLS |
0 |
Set to 1 to use wss:// |
Update in three places consistently:
.env.relay→TUNNEL_SECRET.env.outbound→RELAY_PATH(?secret=...)xray-client.json→wsSettings.path(?secret=...)
The UUID your-uuid-here is the VLESS user ID shared between the client and xray-exit.json. Generate a new one with xray uuid if needed, and update it in both xray-client.json and xray-exit.json.
Stuck on connecting / port 3000 blocked
ISPs in restricted regions block non-standard ports. Put Cloudflare in front of your.relay-domain.com (proxied, orange cloud) and connect on port 443 with TLS enabled. Cloudflare will forward to your host on port 80 via Apache reverse proxy.
no exit worker available on relay
All pool workers are busy or outbound.js is not running. Increase POOL_SIZE in .env.outbound or restart outbound.js.
Connection drops after ~20s idle
The host is terminating idle WebSocket connections. Decrease PING_MS in .env.relay to 15000.
not SOCKS5 ver=N errors on outbound
TARGET_HOST:TARGET_PORT is not pointing at a running xray exit server instance. Check xray -c xray-exit.json is running on the exit server.