Use Netlify's global edge network as a CDN-fronting / reverse-proxy layer in front of your VPS — similar to how Cloudflare Workers or gCore are used to hide an Xray / 3x-ui server from Deep Packet Inspection (DPI).
[Xray Client] ──TLS──► [Netlify Edge Function] ──► [Your VPS / 3x-ui]
(this repo)
Netlify Edge Functions run on Deno at the edge. This worker accepts every incoming request and forwards it to your backend, preserving the full request path, headers, and body. It therefore acts as a transparent reverse proxy that supports all the transports Xray Core uses.
| Transport | Works? | Notes |
|---|---|---|
| WebSocket (ws / wss) | ✅ | Full bidirectional proxying with message queuing |
| xHTTP / SplitHTTP | ✅ | Chunked-streaming POST + GET forwarded as-is |
| gRPC-web | ✅ | application/grpc-web* content-type proxied over HTTP/1.1 |
| HTTP Upgrade | ✅ | Upgrade: websocket is handled natively; other Upgrade values are forwarded as regular HTTP headers |
| Native gRPC (HTTP/2) | Netlify's edge may downgrade to HTTP/1.1; use gRPC-web in the Xray config instead | |
| QUIC / UDP | ❌ | Not available on edge functions |
Or manually:
- Fork this repository.
- Go to https://app.netlify.com → Add new site → Import an existing project.
- Connect your fork. No build command is required.
- Netlify auto-deploys the edge function on every push.
In the Netlify dashboard go to Site settings → Environment variables and add:
| Variable | Required | Description |
|---|---|---|
TARGET_HOST |
Yes | VPS hostname or IP, e.g. 1.2.3.4 |
TARGET_PORT |
No | Backend port (default 443) |
TARGET_HTTPS |
No | true (default) or false — whether to use TLS when connecting to the backend |
Tip: set
TARGET_HTTPS=falseandTARGET_PORT=<port>when Xray listens on a plain HTTP/WS port and Netlify terminates TLS for the client.
After saving the variables, trigger a new deploy (or use Deploys → Trigger deploy).
The Netlify site URL becomes the CDN front for your server. The proxy forwards the full path unchanged, so the Xray inbound path must match what the client connects to.
{
"inbounds": [{
"protocol": "vmess",
"settings": { "clients": [{ "id": "YOUR-UUID" }] },
"streamSettings": {
"network": "ws",
"wsSettings": { "path": "/ws" },
"security": "none"
}
}]
}Client-side Xray config:
{
"outbounds": [{
"protocol": "vmess",
"settings": {
"vnext": [{
"address": "YOUR-APP.netlify.app",
"port": 443,
"users": [{ "id": "YOUR-UUID", "alterId": 0 }]
}]
},
"streamSettings": {
"network": "ws",
"security": "tls",
"tlsSettings": { "serverName": "YOUR-APP.netlify.app" },
"wsSettings": { "path": "/ws" }
}
}]
}"streamSettings": {
"network": "xhttp",
"xhttpSettings": { "path": "/xhttp", "mode": "auto" },
"security": "none"
}Client points to YOUR-APP.netlify.app:443 with TLS + path /xhttp.
Set grpcSettings.serviceName to any string and enable multiMode: false.
Point the client at YOUR-APP.netlify.app:443.
The edge function (netlify/edge-functions/proxy.ts) runs at every Netlify
PoP worldwide. It:
- Reads
TARGET_HOST,TARGET_PORT, andTARGET_HTTPSfrom environment variables. - For requests with
Upgrade: websocket— performs a Deno native WebSocket upgrade, opens a WebSocket to the backend, and bridges both sockets bidirectionally (with a message queue so no data is lost during connection setup). - For all other requests (HTTP, gRPC-web, xHTTP chunked streams, …) — uses
fetch()with a streaming body/response so data flows without buffering. - Strips hop-by-hop headers (per RFC 7230) in both directions.
npm install -g netlify-cli # if not already installed
cp .env.example .env # fill in TARGET_HOST etc.
netlify dev # starts a local edge-function dev serverTARGET_HOSTis the only backend this proxy will connect to. It is not an open proxy.- Always keep
TARGET_HOSTprivate; do not commit it to version control. - The VPS should enforce its own authentication (UUID / password in the Xray config).
- For extra obfuscation, set a non-obvious path (e.g.
/a1b2c3d4) in both the Xray server and client configs.