VLESS CDN Setup: WebSocket or XHTTP on Your Own Domain

VLESS CDN Setup: WebSocket or XHTTP on Your Own Domain

Jason Chen
October 14, 2026· 12 min read

A VLESS CDN setup puts a CDN edge between your client and an Ubuntu VPS you control: the client connects to your domain at the edge, the CDN forwards one secret path to your origin, and Xray accepts it only on loopback. This guide uses Xray v26.3.27, Nginx as the origin proxy, and Cloudflare as the example CDN, with one transport chosen from WebSocket or XHTTP.[1]

Key Takeaways

  • Read the CDN's current terms before building anything; a working tunnel does not make the use permitted.
  • Use a strict origin TLS mode such as Cloudflare Full (strict), never a mode that skips certificate validation.
  • Keep Xray on 127.0.0.1 and let Nginx forward only one secret path per transport.
  • Treat XHTTP packet-up as the conservative CDN choice and WebSocket as the simpler, older one.
  • Separate "the client connected" from "traffic used the CDN": test direct-origin, wrong-path, and wrong-UUID refusals.

This procedure is based on the official sources listed below; a complete end-to-end test has not been performed. Version numbers describe the documentation used, not an author-tested environment. Before you start, define ownership, access, and recovery with the self-hosting planning checklist.

Should your VLESS CDN setup exist at all?

Run this only on a VPS and a domain you own or administer. You need console access that does not depend on the proxy, SSH with strict host-key checking against a verified known_hosts, and a backup of the current Nginx, firewall, and Xray files. Do not borrow someone else's domain, a free subdomain service, or a third-party Worker to host your entry.

A CDN changes who can see and terminate your traffic. The edge decrypts TLS from the client and opens a second TLS connection to your origin, so the CDN operator handles your proxy stream in between. If you only need a dependable daily exit and do not want to maintain certificates, Nginx, and Xray yourself, check whether a managed VPN would save that upkeep before adding another moving part. For how edges and origins relate in general, see how a CDN sits between you and a site.

Check the CDN terms before the configuration

Cloudflare's service-specific terms reserve the right to disable or limit CDN use that serves video or a disproportionate share of large files without the relevant paid services.[2] A proxy that carries streaming or bulk downloads can fall into that pattern. Read the current terms and plan limits for your account, and keep a direct entry such as a VLESS REALITY server if you need a path that does not depend on the CDN's policy.

Nothing here makes a route unblockable. The CDN can restart edge servers, apply idle timeouts, or change features, and Cloudflare states that releases can terminate open WebSocket connections.[3]

WebSocket or XHTTP: which transport fits the CDN path?

Both transports carry VLESS inside HTTP, so a CDN that proxies HTTP can forward them. They differ in how the stream is framed and in what the CDN must allow.

QuestionVLESS WebSocket TLSVLESS XHTTP CDN (packet-up)
How data movesOne upgraded HTTP/1.1 connectionUpload as numbered POST requests, download as a streaming GET
CDN requirementWebSockets enabled for the zonePlain HTTP proxying; no WebSocket upgrade
Project X guidanceDocs recommend moving to XHTTP because WebSocket's ALPN is http/1.1packet-up described as the most compatible mode behind CDNs and proxies
Origin proxy needsUpgrade and Connection headersUnbuffered request and response bodies
Main trade-offSimple and widely supported by clientsNewer; client and server cores must both support it

Project X's WebSocket page recommends switching to XHTTP to avoid WebSocket's distinctive http/1.1 ALPN.[4] The XHTTP transport entry points to the project's XHTTP discussion, which recommends packet-up when a CDN or reverse proxy will not pass the other modes, and notes that stream-up through Cloudflare needs gRPC support enabled.[5][6] This guide keeps XHTTP at packet-up so the origin can stay a normal HTTP/1.1 proxy. If you already run a direct XHTTP entry, keep it separate from this CDN entry.

Step 1: Prepare the domain, certificate, and origin firewall

Create a DNS record such as proxy.example.com pointing to your VPS and set it to proxied through the CDN. In Cloudflare, set the zone's SSL/TLS mode to Full (strict): the edge then validates your origin certificate, which can come from a public CA or from Cloudflare Origin CA.[7] Do not use Flexible or Full, which either skip origin TLS or accept invalid certificates.

If you choose WebSocket, turn on WebSockets in the zone's Network settings.[3] Install the origin certificate and key at /etc/ssl/vless-cdn/fullchain.pem and /etc/ssl/vless-cdn/privkey.pem, owned by root, with the key at mode 0600.

Limit TCP 443 on the origin to the CDN's published address ranges so that clients cannot skip the edge.[8] Keep your SSH rule and any certificate-renewal path that still needs to reach the server. Re-check those ranges when the CDN updates them; a stale list can break the entry without warning.

Step 2: Run Xray inbounds on loopback only

Download the Linux 64-bit archive from the official v26.3.27 release, check its release notes and published verification material, and install the binary at /opt/xray-cdn/v26.3.27/xray, root-owned, mode 0755. Generate a UUID with /opt/xray-cdn/v26.3.27/xray uuid and pick a random path segment for each transport. Create a non-login account xray-cdn and save this as /etc/xray-cdn/server.json, mode 0640, root:xray-cdn.[1]

{
  "log": {"loglevel": "warning"},
  "inbounds": [
    {
      "tag": "vless-ws",
      "listen": "127.0.0.1",
      "port": 10001,
      "protocol": "vless",
      "settings": {"clients": [{"id": "REPLACE_UUID"}], "decryption": "none"},
      "streamSettings": {"network": "ws", "wsSettings": {"path": "/ws-REPLACE_SECRET"}}
    },
    {
      "tag": "vless-xhttp",
      "listen": "127.0.0.1",
      "port": 10002,
      "protocol": "vless",
      "settings": {"clients": [{"id": "REPLACE_UUID"}], "decryption": "none"},
      "streamSettings": {"network": "xhttp", "xhttpSettings": {"path": "/xh-REPLACE_SECRET/", "mode": "packet-up"}}
    }
  ],
  "outbounds": [{"tag": "direct", "protocol": "freedom"}]
}

Keep only the inbound for the transport you chose. Neither inbound enables TLS, because TLS ends at Nginx on the same host; that is safe only while both listen on 127.0.0.1. VLESS decryption: none means VLESS adds no encryption of its own. Validate with sudo -u xray-cdn /opt/xray-cdn/v26.3.27/xray run -test -config /etc/xray-cdn/server.json, then run it as a systemd service under the xray-cdn account.

Step 3: Forward only the secret paths with Nginx

Add a server block for proxy.example.com. Every path other than your secret ones returns 404, so a scanner at the origin sees an ordinary HTTPS site with nothing behind it.

server {
    listen 443 ssl;
    server_name proxy.example.com;
    ssl_certificate     /etc/ssl/vless-cdn/fullchain.pem;
    ssl_certificate_key /etc/ssl/vless-cdn/privkey.pem;

    location = /ws-REPLACE_SECRET {
        proxy_pass http://127.0.0.1:10001;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 300s;
    }

    location /xh-REPLACE_SECRET/ {
        proxy_pass http://127.0.0.1:10002;
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_request_buffering off;
        proxy_set_header Host $host;
        proxy_read_timeout 300s;
    }

    location / { return 404; }
}

The WebSocket block passes the Upgrade and Connection headers that a hop-by-hop proxy otherwise drops.[9] The XHTTP block turns off buffering in both directions; with proxy_http_version 1.1, Nginx can pass a request body to Xray as it arrives instead of collecting it first.[10] The XHTTP location uses a prefix match because each request adds its own suffix to the path. Run sudo nginx -t and reload only after it passes.

Step 4: Configure the client through the CDN hostname

On an Ubuntu client, install the same Xray version and save a private client.json (mode 0600) for the WebSocket route:

{
  "inbounds": [{"listen": "127.0.0.1", "port": 1083, "protocol": "socks", "settings": {"auth": "noauth", "udp": false}}],
  "outbounds": [{
    "tag": "cdn-ws",
    "protocol": "vless",
    "settings": {"vnext": [{"address": "proxy.example.com", "port": 443, "users": [{"id": "REPLACE_UUID", "encryption": "none"}]}]},
    "streamSettings": {
      "network": "ws",
      "security": "tls",
      "tlsSettings": {"serverName": "proxy.example.com", "allowInsecure": false},
      "wsSettings": {"path": "/ws-REPLACE_SECRET"}
    }
  }]
}

For XHTTP, keep the same outbound and replace the transport fields:

"network": "xhttp",
"xhttpSettings": {"path": "/xh-REPLACE_SECRET/", "mode": "packet-up"}

The address and serverName are your domain, not the origin IP, so the client's TLS session ends at the edge and certificate validation stays on. Run xray run -test -config ./client.json, then start the client in the foreground and point one test application at SOCKS5 127.0.0.1:1083.

How do you prove traffic really goes through the CDN?

A page loading through the proxy only proves that some path works. Check each boundary separately, changing one thing per test and restoring the working file in between.

Check methodExpected behaviorIf it differs
curl --proxy socks5h://127.0.0.1:1083 https://check.example.net/health against your own endpointExpected response through the proxyRead Nginx and Xray logs before changing transport
dig +short proxy.example.comReturns CDN edge addresses, not your VPS IPRecord is not proxied; fix DNS before trusting other results
From an outside host: curl --resolve proxy.example.com:443:ORIGIN_IP https://proxy.example.com/Connection times out or is refusedOrigin firewall still admits non-CDN clients
Request https://proxy.example.com/wrong-path through the edge404 from NginxA broad location block is forwarding too much
Client copy with another valid UUIDProxy request failsCheck that the old client process is stopped and the UUID list is correct
Client copy with a serverName that is not on the edge certificateTLS validation failsKeep allowInsecure: false; never "fix" this by allowing insecure TLS

Then open the IP address tool in a browser configured for the local SOCKS port. It should show your VPS egress address, because the CDN carries the inbound leg while Xray's freedom outbound leaves from the server. That result covers the browser request only, not DNS, IPv6, or UDP from other apps.

If WebSocket requests fail while ordinary HTTPS through the same edge works, the WebSocket-versus-HTTPS diagnosis helps separate CDN settings, proxies, and client faults. For XHTTP, a CDN that closes downloads after idle periods shows up as long requests dropping; the XHTTP discussion notes that Cloudflare cuts HTTP responses that carry no data for 100 seconds.[6]

How do you roll back or retire the CDN entry?

Before changes, keep private copies of server.json, the Nginx site file, the firewall rules, and the client file. Rolling back the client means restoring its previous profile and confirming a fresh request. On the server, disable the new Nginx site and the xray-cdn service, reload Nginx, and confirm with sudo ss -lntp that nothing listens on ports 10001 or 10002.

To retire the entry, remove the proxied DNS record or switch it off the CDN, delete the UUID from any remaining configuration, and revoke or delete the origin certificate if nothing else uses it. Remove the CDN-only firewall rules last, after you know which other sites on the server still depend on port 443. Upgrades follow the same pattern: stage a new version directory, run -test on both files, and repeat the verification table before switching the service.

Summary

  • Confirm the CDN's terms and your own recovery access before configuring anything.
  • Use a strict origin TLS mode and keep Xray reachable only through Nginx on loopback.
  • Choose WebSocket for simplicity or XHTTP packet-up for the conservative CDN path, not both on one client.
  • Prove the CDN path with refusal tests, not just one successful page load.

FAQ

Is a CDN in front of VLESS more private than a direct entry?

Not automatically. The CDN terminates the client's TLS and opens its own connection to your origin, so it can see the proxied HTTP stream that carries your VLESS traffic.

Why not use Cloudflare's Flexible SSL mode to save time?

Flexible sends traffic from the edge to your origin without TLS. Full (strict) validates the origin certificate, which protects the second leg and stops impersonation of your origin.

Can I run WebSocket and XHTTP on the same domain?

Yes, with separate secret paths and separate Nginx locations, as in the example. Give each client one transport so you know which path a failure belongs to.

Why does XHTTP use packet-up here instead of auto?

The project's guidance names packet-up as the most compatible mode behind CDNs and reverse proxies, and it keeps the origin as plain HTTP/1.1 without gRPC settings.

Does hiding the origin behind a CDN stop all direct scans?

Only if the origin firewall admits the CDN's address ranges alone. Without that rule, anyone who learns the VPS address can reach Nginx directly and bypass the edge.

Will this keep my proxy reachable under every network block?

No. A CDN-fronted entry can still be throttled, filtered, or limited by the CDN itself, and edge restarts close open connections. Keep a separate recovery path.

How often should I update the origin firewall ranges?

Check the CDN's published ranges on a schedule and after any notice from the provider. An outdated allow list can silently reject legitimate edge connections to your origin.

Disclaimer: This article is for general informational purposes only and does not constitute legal, technical, or other professional advice. We make no guarantees regarding the accuracy, completeness, or timeliness of the content.

Sources:

  1. Xray-core release v26.3.27 — https://github.com/XTLS/Xray-core/releases/tag/v26.3.27
  2. Cloudflare Service-Specific Terms — https://www.cloudflare.com/service-specific-terms-application-services/
  3. Cloudflare Docs: WebSockets — https://developers.cloudflare.com/network/websockets/
  4. Project X: WebSocket transport — https://xtls.github.io/en/config/transports/websocket.html
  5. Project X: XHTTP (SplitHTTP) transport entry — https://xtls.github.io/en/config/transports/splithttp.html
  6. Project X: XHTTP: Beyond REALITY — https://github.com/XTLS/Xray-core/discussions/4113
  7. Cloudflare Docs: SSL/TLS encryption modes — https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/
  8. Cloudflare IP ranges — https://www.cloudflare.com/ips/
  9. NGINX: WebSocket proxying — https://nginx.org/en/docs/http/websocket.html
  10. NGINX: ngx_http_proxy_module — https://nginx.org/en/docs/http/ngx_http_proxy_module.html

Sources checked 14 October 2026.

Related Articles:

Start your 3-day free trial

Sign up to experience all premium features at no cost.

*Available only to new users. Each user is limited to one trial.

VLESS CDN Setup: WebSocket or XHTTP on Your Own Domain | AethoVPN