Coexistence of Web Applications and VLESS+TCP+XTLS VISION

The latest stable Xray configuration: VLESS-TCP-XTLS-Vision

Coexistence of Web Applications and VLESS+TCP+XTLS VISION

1. Intro

Xray-core is a superset of v2ray-core, with better overall performance and a series of enhancements such as XTLS, and is fully compatible with the functions and configurations of v2ray-core.

  • There is only one executable file, including ctl function, “run” is the default command.
  • The configuration is fully compatible, and the corresponding environment variables and API should be changed to start with XRAY_.
  • Open ReadV of bare protocol on all platforms.
  • Provide complete VLESS & Trojan XTLS support, both have ReadV.

Special thanks to the founder of Xray: @rprx, as well as all the amazing contributors.

2. Background

From Xray-core v1.8.0, the flow xtls-rprx-direct will be deprecated. The new flow is called xtls-rprx-vision. In this case, if you prefer to use XTLS feature, you may choose:

  • No flow, empty character or none: use normal TLS proxy
  • xtls-rprx-vision: use new XTLS modes include inner handshake random padding support uTLS simulated client fingerprinting
  • xtls-rprx-vision-udp443: Same as xtls-rprx-vision, but allows UDP traffic destined for port 443

Major clients will no longer support xtls-rprx-direct. Such as v2rayN will start to only support xtls-rprx-direct starting from v6.18

In this article, I will introduce the way to use Nginx performing SNI offloading, so that port 443 can be reused, and eventually multiple web applications and Xray can share port 443 simultaneously.

3. Deployment

3.1 Premise

In this article, I assume you are deploying:
(1) a web app, in this article, NextCloud
(2) a static page, in this article, a webgame

3.2 Install dependencies

First, you need to install Nginx and the stream module of Nginx. This module has a package on Ubuntu that can be installed directly.

apt -y update
apt -y install curl git nginx libnginx-mod-stream python3-certbot-nginx

Run the following script to install(and further upgrade) Xray.

bash -c "$(curl -L" @ install

(Optional) Uninstall script

bash -c "$(curl -L" @ remove

3.3 Config the Nginx main conf.d

nano /etc/nginx/nginx.conf

Add the following configuration outside the “http block”

stream {
        map $ssl_preread_server_name $example_multi {
        upstream xtls {
                server; # Xray port
        upstream nextcloud {
                server; # Nextcloud port
        server {
                listen 443      reuseport;
                listen [::]:443 reuseport;
                proxy_pass      $example_multi;
                ssl_preread     on;

In the above configuration, there are two domains.

The first upstream domain name is required, for the purpose of Xray. The second (or the third to Nth) upstream(s) are the domain names of other websites, in this case, NextCloud.

It is particularly important to note that since port 443 is monitored in the stream block, other Nginx configuration files can no longer listen to port 443, otherwise an error will be reported when Nginx starts.

In addition, to ensure SNI offloading working normally, the premise is that the “listen” on port 443 in your other Nginx configuration files needs to be changed to the port you specify in upstream, in this case, port 20002.

3.4 Config fallback

Next, build a fallback site for VLESS, which also can be called a “camouflage site”. You can simply put a static page here. Here I found a small game, 2048.

cd /var/www/html
git clone 2048

Now create a new Nginx configuration file for the fallback site:

nano /etc/nginx/conf.d/fallback.conf

Write the following configuration:

server {
        listen 80;
        if ($host = {
                return 301 https://$host$request_uri;
        if ($host = {
                return 301 https://$host$request_uri;
        return 404;

server {
        listen http2 proxy_protocol;
        index index.html;
        root /var/www/html/2048;

It should be noted here that the fallback site does not need to configure SSL. If VLESS drops the request back to this site, this site automatically supports SSL.

You only need to ensure that the server_name of the fallback site and the Xray server domain name configured in the stream block are the same. For example in this article:

In addition, like I said before, “listen” cannot listen on port 443. Here you can change any other unoccupied port, such as port 20009 in the above configuration, then port 20009 is the fallback port.

3.5 Config SSL

Now we need to use certbot to issue an SSL certificate. As mentioned before, SSL is not required for the fallback site, so certbot here uses the following command to generate only the certificate without modifying the nginx configuration file:

certbot certonly --nginx

Copy the generated certificate to the Xray configuration directory:

cp /etc/letsencrypt/live/ /usr/local/etc/xray/fullchain.pem
cp /etc/letsencrypt/live/ /usr/local/etc/xray/privkey.pem

Since Xray is managed by systemd, and the user in systemd is nobody, in this case the owner of the certificate should also be changed to nobody, otherwise Xray has no permission to read the certificate file:

chown nobody:nogroup /usr/local/etc/xray/fullchain.pem
chown nobody:nogroup /usr/local/etc/xray/privkey.pem

3.6 Config Xray

Generate and copy an UUID

xray uuid

Edit the Xray config.json

nano /usr/local/etc/xray/config.json

Make config.json empty and then config as follows:

    "log": {
        "loglevel": "warning"
    "routing": {
        "domainStrategy": "IPIfNonMatch",
        "rules": [
                "type": "field",
                "ip": [
                "outboundTag": "block"
    "inbounds": [
            "listen": "", // "" Indicates listening to both IPv4 and IPv6
            "port": 20001, // The port on which the server listens
            "protocol": "vless",
            "settings": {
                "clients": [
                        "id": "2ae57550-bbc5-470b-9b60-f160ea0fa4c2", // User ID, perform xray uuid generation, or a string of 1-30 bytes
                        "flow": "xtls-rprx-vision"
                        "id": "7136be22-b9da-461b-9f77-27aaeaf999d3", // User ID, perform xray uuid generation, or a string of 1-30 bytes
                        "flow": "xtls-rprx-vision"
                "decryption": "none",
                "fallbacks": [
		"alpn": "h2", // indicates http2 protocol
                        "dest": "20009",
                        "xver": 1
            "streamSettings": {
                "network": "tcp",
                "security": "tls", // put tls instead of xtls here
                "tlsSettings": {
                    "rejectUnknownSni": true, // your choice, better go with true
                    "minVersion": "1.2",
                    "certificates": [
                            "ocspStapling": 3600,
                            "certificateFile": "/usr/local/etc/xray/fullchain.pem", // For the certificate file, it is recommended to use fullchain (full SSL certificate chain). If there is only a website certificate, v2rayN can be used but v2rayNG cannot be used. Usually, the extension is not distinguished
                            "keyFile": "/usr/local/etc/xray/privkey.pem" // private key file
            "sniffing": {
                "enabled": true,
                "destOverride": [
    "outbounds": [
            "protocol": "freedom",
            "tag": "direct"
            "protocol": "blackhole",
            "tag": "block"
    "policy": {
        "levels": {
            "0": {
                "handshake": 2, // The handshake time limit when the connection is established, in seconds, the default value is 4, it is recommended to be different from the default value
                "connIdle": 120 // Connection idle time limit in seconds, the default value is 300, it is recommended to be different from the default value

3.7 Install NextCloud via Docker

Install Docker

curl -sSL | sh
systemctl enable docker

Install Docker Compose (check latest version yourself)

curl -L`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

3.8 Config docker-compose file of NextCloud

In this case I’m using the Base version (apache) of NextCloud Docker image, because This setup provides no SSL encryption and is intended to run behind a proxy.

nano docker-compose.yml
version: '3'


    image: mariadb
    restart: always
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
      - db:/var/lib/mysql
      - MYSQL_ROOT_PASSWORD=your_root_password
      - MYSQL_PASSWORD=your_password
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud

    image: nextcloud
    restart: always
      - # this local port 20010 is to be proxied by Nginx
      - db
      - nextcloud:/var/www/html
      - MYSQL_PASSWORD=your_password
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_HOST=db

Run docker-compose

docker-compose up -d

3.9 Config nextcloud.conf

So the main nginx.conf includes:
(1) fallback.conf, for Xray fallback purpose;
(2) nextcloud.conf, for NextCloud deployment.

nano /etc/nginx/conf.d/nextcloud.conf

Edit the .conf file as follows

server {
  listen http2 ssl; # match the NextCloud port setting in 3.3

  ssl_certificate       /etc/letsencrypt/live/;
  ssl_certificate_key   /etc/letsencrypt/live/;
#ssl_dhparam          /your/path/dhparams.pem; #optional
#Authenticated Origin Pull is optional. Please refer to
#ssl_client_certificate  /etc/ssl/origin-pull-ca.pem;
#ssl_verify_client on;
  ssl_session_timeout 1d;
  ssl_session_cache shared:MozSSL:10m;
  ssl_session_tickets off;

  ssl_protocols         TLSv1.2 TLSv1.3;
  ssl_prefer_server_ciphers off;

  server_name ;
    add_header Content-Security-Policy upgrade-insecure-requests;
  location / {
    proxy_redirect off;
    proxy_pass; #The port 20010 must match the port setting of Nextcloud docker-compose file. 
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Note that the certificate file and key file in /usr/local/etc/xray/ is different from the ones in /etc/letsencrypt/live/
You can use certbot again to issue certs. Also feel free to try CloudFlare Original Certificate. The tutorial is GHOST_URL/use-cloudflare-certificate-with-nginx-v2ray-websocket-tls-cdn/. If you choose to use CloudFlare Original CA, you have to turn DNS proxy ON.

3.10 Test run

Check Nginx syntax

nginx -t

Check Xray syntax

cd /usr/local/etc/xray/
xray --test

After confirming that the above configurations are correct, set Nginx and Xray to start automatically

systemctl enable nginx xray

Restart Nginx and Xray to make the new configuration take effect

systemctl restart nginx xray

4. Client Setting

In this article we use Qv2ray as an example, for it now supports Xray-core & XTLS, also its advantage of cross-platform.

  • Host:
  • Port: 443
  • Type: VLESS
  • UUID: the one in config.json
  • Flow: xtls-rprx-vision
  • Transport Protocal: tcp
  • Security Type: tls

Generally speaking, a preferable configuration includes:

  • The server uses a reasonable port and prohibits traffic returning to the country
  • Only configure XTLS Vision, not compatible with ordinary TLS proxy
  • Fall back to the web page, do not fall back/distribute to other proxy agreements
  • Client enables uTLS (fingerprint)


If you have any question, feel free to leave a comment, or contact me by [email protected]

Copyright statement: Unless otherwise stated, all articles on this blog adopt the CC BY-NC-SA 4.0 license agreement. For non-commercial reprints and citations, please indicate the author: Henry, and original article URL. For commercial reprints, please contact the author for authorization.