xjkj8

Nginx in front of VLESS XHTTP

In this article, Nginx sits in front of Xray, which implements the VLESS protocol with XHTTP transport. Transport Layer Security (TLS) is processed by Nginx before traffic reaches Xray. This makes your proxy server resemble a realistic web server. The Nginx and Xray configurations are almost entirely copied from lxhao61/integrated-examples. This article fleshes out the practical details of how to install and configure the software.

1. Set up server

1.1. Create DNS records

You will need to purchase a domain name to implement the configuration in this article.

The configurations in this article allow you to reach the server either directly or through a content distribution network (CDN). This will require two subdomains, both mapped to your server's IP address:

Depending on your requirements, you can either follow the example exactly and support both options, or eliminate the one you do not need.

1.2. Open firewall

Open your server's firewall and/or security groups for input:

1.3. Install Xray

SSH into your server as root. On Windows, you can do this with Windows PowerShell. On macOS or Linux, you can use your built-in terminal emulator.

ssh [email protected]

Update the server:

apt update && apt upgrade -y && apt autoremove -y

As explained in XTLS/Xray-install, install Xray by running the script:

bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install

1.4. Choose parameters

Generate a universally unique id (UUID) to serve as a password for VLESS:

xray uuid

Example:

8104b4b2-976c-4e53-97db-f654fc8620fb

Generate a secret path for reaching Xray through Nginx:

< /dev/urandom tr -dc a-z0-9 | head -c${1:-8};echo;

Example:

4qgjtkw3

1.5. Configure Xray

Edit the Xray confuration file:

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

Use the model below, replacing the sample values by your own values. Xray will listen only on localhost to prevent unauthorized visitors from detecting its presence. Port 127.0.0.1:2023 was chosen as an example. You can change the port number if you wish, as long as you are consistent between Xray and Nginx. Change the sample UUID and path in the example to your actual chosen UUID and path.

{
  "log": {
    "loglevel": "warning",
    "error": "/var/log/xray/error.log",
    "access": "/var/log/xray/access.log"
  },
  "inbounds": [
    {
      "listen": "127.0.0.1", 
      "port": 2023,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "8104b4b2-976c-4e53-97db-f654fc8620fb"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "xhttp",
        "xhttpSettings": {
          "path": "/4qgjtkw3"
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": [
          "http",
          "tls",
          "quic"
        ]
      }
    }
  ],
  "routing": {
    "rules": [
      {
        "type": "field",
        "protocol": [
          "bittorrent"
        ],
        "outboundTag": "block"
      }
    ]
  },
  "outbounds": [
    {
      "protocol": "freedom",
      "settings": {}
    },
    {
      "tag": "block",
      "protocol": "blackhole",
      "settings": {}
    }
  ]
}

Save the file with your changes in it.

1.6. Start Xray

Restart Xray with your new configuration:

systemctl restart xray

Check that Xray is active (running):

systemctl status xray

1.7. Install Nginx

The easiest way to install Nginx is to use your Linux distribution's repositories. You can certainly do that. However, the version of Nginx in your distribution's repositories may be quite old. Therefore what follows are instructions for installing Nginx direct from the Nginx project's repositories. This will give you an up-to-date version. The instructions here are a copy of what appears on nginx.com.

First install the prerequisites:

apt install -y curl gnupg2 ca-certificates lsb-release debian-archive-keyring

Download the official Nginx signing key:

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

Display the codename of your distribution:

lsb_release -cs

Example:

bookworm

Create a file /etc/apt/sources.list.d/nginx.list:

vi /etc/apt/sources.list.d/nginx.list

Insert an entry:

deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian bookworm nginx

Save the file:

Set up repository pinning to prefer the Nginx project's packages over your distribution's ones. Create a file /etc/apt/preferences.d/99nginx:

vi /etc/apt/preferences.d/99nginx

Insert these contents (with a blank line at the end):

Package: *
Pin: origin nginx.org
Pin: release o=nginx
Pin-Priority: 900

Save the file.

Update your package metadata for the new repository:

apt update

Install Nginx:

apt install nginx -y

Check which version of Nginx you have:

nginx -v

Example:

nginx version: nginx/1.26.2

Enable, start, and check the status of Nginx:

systemctl enable nginx

systemctl start nginx

systemctl status nginx

1.8. Obtain SSL certificate

Install the snap daemon:

apt install -y snapd

Install the certbot snap:

snap install --classic certbot

Create a symbolic link to the certbot binary:

ln -s /snap/bin/certbot /usr/bin/certbot

Invoke certbot. You need certonly because you are going to configure Nginx yourself. You need --nginx so that certbot can share port tcp/80 with Nginx. Of course, you must specify your own domains in place of the examples given here. Each subdomain must be requested separately.

certbot certonly --nginx --agree-tos --register-unsafely-without-email -d h3.xjkj8.xyz

The certificate is saved at /etc/letsencrypt/live/h3.xjkj8.xyz/fullchain.pem. The key is saved at /etc/letsencrypt/live/h3.xjkj8.xyz/privkey.pem.

certbot certonly --nginx --agree-tos --register-unsafely-without-email -d z1.xjkj8.xyz

The certificate is saved at /etc/letsencrypt/live/z1.xjkj8.xyz/fullchain.pem. The key is saved at /etc/letsencrypt/live/z1.xjkj8.xyz/privkey.pem.

Test certificate renewal with a dry run:

certbot renew --dry-run

1.9. Configure Nginx

Edit the Nginx configuration file:

vi /etc/nginx/nginx.conf

Completely delete the existing contents.

Insert your own contents, using the following as a model. Go through the model carefully, line by line, changing it to fit your environment and your needs.

# The commented out user implies Nginx will run as the default user (root). 
# If you uncomment and edit the next line, 
# make sure the user and group have the correct permissions.
#user nobody nogroup; 

worker_processes auto;

error_log /var/log/nginx/error.log; 

pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;

    # Use the map function to let the custom variable $clientRealIp get the client's real IP address. 
    # The configuration is as follows (this is optional):
    map $http_x_forwarded_for $clientRealIp {
        "" $remote_addr;
        "~*(?P<firstAddr>([0-9a-f]{0,4}:){1,7}[0-9a-f]{1,4}|([0-9]{1,3}\.){3}[0-9]{1,3})$" $firstAddr;
    }

    # Redefine the access log format.
    # The configuration is as follows (this is optional):
    log_format main '$clientRealIp $remote_addr $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" $http_x_forwarded_for '
                    '"$upstream_addr" "$upstream_status" "$upstream_response_time" "$request_time" ';

    access_log /var/log/nginx/access.log main;

    sendfile on;

    keepalive_timeout 65;

    server {
        listen 80;
        # If your server has no IPv6, the next line can be deleted.
        listen [::]:80; 
        # Automatically redirect HTTP to HTTPS, to make the website look more authentic.
        return 301 https://$host$request_uri; 
    }

    server {

        # Listen statements for where the version is not less than v1.25.1.
        # Otherwise they must be deleted.
        # You can delete the IPv6 line if your server has no IPv6.
        listen 443 ssl default_server; 
        listen [::]:443 ssl default_server; 
        http2 on; 

        # Listen statements for where the version is is less than v1.25.1.
        # Otherwise they must be deleted.
        # You can delete the IPv6 line if your server has no IPv6.
        listen 443 ssl http2 default_server; 
        listen [::]:443 ssl http2 default_server; 

        ssl_protocols TLSv1.2 TLSv1.3;

        # The next line is supported only for versions no less than v1.19.4.
        # It limits domain name connections 
        # (including prohibiting access to websites via IP).
        ssl_reject_handshake on; 

    } 

        # If the certificates in the server block above 
        # and the certificates in the server block below
        # use the same wildcard certificate 
        # or Subject Alternative Name (SAN) certificate, 
        # the next part of the configuration can be deleted 
        # and the related configuration details merged.

    server {

        # Listen statements for where the version is not less than v1.25.1.
        # Otherwise they must be deleted.
        # You can delete the IPv6 line if your server has no IPv6.
        listen 443 ssl; 
        listen [::]:443 ssl; 
        http2 on; 

        # Listen statements for where the version is is less than v1.25.1.
        # Otherwise they must be deleted.
        # You can delete the IPv6 line if your server has no IPv6.
        listen 443 ssl http2; 
        listen [::]:443 ssl http2; 

        # Change the server name to your own domain name
        server_name z1.xjkj8.xyz;

        # Replace with your own certificate's absolute path.
        ssl_certificate /etc/letsencrypt/live/z1.xjkj8.xyz/fullchain.pem; 
        # Replace with your own key's absolute path.
        ssl_certificate_key /etc/letsencrypt/live/z1.xjkj8.xyz/privkey.pem; 

        # If using the OpenSSL library, TLSv1.3 requires 
        # the OpenSSL library version to be no less than 1.1.1 to be supported.
        ssl_protocols TLSv1.2 TLSv1.3; 

        # Prioritize the server's cipher suite 
        # (valid for the following TLSv1.2 protocol cipher suites).
        ssl_prefer_server_ciphers on; 

        # If the certificate is an RSA certificate, change all ECDSA to RSA.
        ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305; 

        # If using the OpenSSL library, this configuration parameter 
        # requires the OpenSSL library version to be 
        # no less than 3.0.0 to be supported.
        ssl_ecdh_curve secp521r1:secp384r1:secp256r1:x25519; 

        # The location path corresponds to the path 
        # in the VLESS XHTTP application
        location /4qgjtkw3 { 
            proxy_http_version 1.1;
            # Forward to the local VLESS + XHTTP listening port
            proxy_pass http://127.0.0.1:2023; 
            proxy_redirect off;
            proxy_request_buffering off;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location / {
            # Enable HSTS
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; 
            # Modify the web root file path to your own location, if necessary
            root /var/www/html; 
            index index.html index.htm;
        }
    } 

    server {

        # Listen statements for where the version is not less than v1.25.0 
        # and the SSL library supports QUIC (HTTP/3) configuration, 
        # otherwise they must be deleted.
        # You can delete the IPv6 line if your server has no IPv6.
        listen 443 quic reuseport; 
        listen [::]:443 quic reuseport; 

        # Listen statements for where the version is not less than v1.25.1.
        # Otherwise they must be deleted.
        # You can delete the IPv6 line if your server has no IPv6.
        listen 443 ssl; 
        listen [::]:443 ssl; 
        http2 on;

        # Listen statements for where the version is less than v1.25.1.
        # Otherwise they must be deleted.
        # You can delete the IPv6 line if your server has no IPv6.
        listen 443 ssl http2; 
        listen [::]:443 ssl http2; 

        # Change to your own domain name
        server_name h3.xjkj8.xyz; 

        # Replace with your own certificate's absolute path.
        ssl_certificate /etc/letsencrypt/live/h3.xjkj8.xyz/fullchain.pem;
        # Replace with your own key's absolute path.
        ssl_certificate_key /etc/letsencrypt/live/h3.xjkj8.xyz/privkey.pem; 

        ssl_protocols TLSv1.2 TLSv1.3; 
        # Prioritize the server's cipher suite 
        # (valid for the following TLSv1.2 protocol cipher suites)
        ssl_prefer_server_ciphers on;
        # If the certificate is an RSA certificate, change all ECDSA to RSA.
        ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305; 
        # If using the OpenSSL library, this configuration parameter 
        # requires the OpenSSL library version to be 
        # no less than 3.0.0 to be supported.
        ssl_ecdh_curve secp521r1:secp384r1:secp256r1:x25519; 

        # The location path corresponds to the path 
        # in the VLESS XHTTP application
        location /4qgjtkw3 { 
            proxy_http_version 1.1;
            # Forward to the local VLESS XHTTP listening port
            proxy_pass http://127.0.0.1:2023;
            proxy_redirect off;
            proxy_request_buffering off;
            proxy_set_header Host $host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        location / {
            # Enable HSTS
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; 
            # Announce the availability of HTTP/3 server
            add_header Alt-Svc 'h3=":443"; ma=86400'; 
            # Modify the web root file path to your own location, if necessary
            root /var/www/html; 
            index index.html index.htm;
        }
    }
}

Save the file once you are done making changes.

1.10. Restart Nginx

Check the validity of your Nginx configuration file:

nginx -t

Restart Nginx with your new configuration file:

systemctl restart nginx

Check Nginx status:

systemctl status nginx

1.11. Add camouflage web content

Add some web content to /var/www/html. In the example given here, we will use PavelDoGreat/WebGL-Fluid-Simulation:

curl -L https://github.com/PavelDoGreat/WebGL-Fluid-Simulation/archive/refs/heads/master.zip -O

Extract the archive:

apt install -y unzip

unzip master.zip

Copy the web content into place:

mkdir -p /var/www/html

cp -r WebGL-Fluid-Simulation-master/* /var/www/html

ls -l /var/www/html

You can now exit your SSH session with the server:

exit

1.12. Check HTTP/3 support

Open a browser on your workstation. Attempt to visit https://h3.xjkj8.xyz.

If you can reach your domain, go to https://www.http3check.net. Check your domain https://h3.xjkj8.xyz. Verify that QUIC is supported and HTTP/3 is supported.

2. Set up client

Use either the command-line client or the graphical user interface (GUI) client, as you prefer. We will give instructions for the h3 subdomain.

2.1. Command-line client

Download the CLI client from https://github.com/XTLS/Xray-core/releases. On Windows that will be Xray-windows-64.zip. Unzip the .zip file.

Create a config.json configuration file in the same folder as the app (e.g. Downloads\Xray-windows-64). Use the following as a model. Change the server, path, and UUID to your own values.

{
    "inbounds": [
        {
            "sniffing": {
                "enabled": true,
                "destOverride": [
                    "http",
                    "tls",
                    "quic"
                ]
            },
            "port": 10808,
            "listen": "127.0.0.1",
            "protocol": "socks",
            "settings": {
                "udp": true
            }
        }
    ],
    "outbounds": [
        {
            "protocol": "vless",
            "settings": {
                "vnext": [
                    {
                        "address": "h3.xjkj8.xyz",
                        "port": 443,
                        "users": [
                            {
                                "id": "8104b4b2-976c-4e53-97db-f654fc8620fb", 
                                "encryption": "none"
                            }
                        ]
                    }
                ]
            },
            "streamSettings": {
                "network": "xhttp",
                "security": "tls",
                "xhttpSettings": {
                    "path": "/4qgjtkw3",
                    "host": "h3.xjkj8.xyz" 
                },
                "tlsSettings": {
                    "serverName": "h3.xjkj8.xyz", 
                    "alpn": [
                        "h3"
                    ]
                }
            }
        }
    ]
}

Save the config.json file.

Open a command prompt window.

Run Xray from the command line:

xray.exe -c config.json

Configure your browser or your system-wide proxy to use the SOCKS5 proxy at 127.0.0.1 port 10808.

Check that you can surf the web.

2.2. GUI client

The latest version of v2rayN supports xhttp.

Download the latest version of v2rayN-windows-64-With-Core.zip from https://github.com/2dust/v2rayN.

Unzip the .zip file

Configure v2rayN to use the HTTP/3 subdomain (in this example).

v2rayN configured for Nginx in front of VLESS XHTTP

For greater realism, add a browser fingerprint.

Connect your client to your server in v2rayN.

Configure your browser or your system-wide proxy to use the SOCKS5 proxy at 127.0.0.1 port 10808.

Check that you can surf the web.