Skip to content

VLESS + REALITY + VISION working (first on non-443 port)

In this section, we create a minimal Xray configuration for VLESS + REALITY + VISION and make it work first on a non-443 port (e.g., 4433).

Goal: the client (Android V2rayNG) can connect, and the server access.log shows traffic.


6.1 Decide the first test port and open firewall

Before sharing port 443 with nginx, we first test Xray on a dedicated port to isolate any configuration issues .

  • pick a test port (example: 4433)
  • ensure this port is allowed in your firewall:
    shell
    sudo ufw allow 4433/tcp

6.2 Generate REALITY keys (x25519)

REALITY requires an x25519 key pair. Note: Newer Xray versions use confusing terminology in their output .

  • generate the keys:
    shell
    /usr/local/bin/xray x25519
  • the output will look like this :
    shell
    PrivateKey: EOM_0M2Sw-YOUR_PRIVATE_KEY_LYApCNUskI7qfymc
    Password: BwtllsQkQiYOURPASSWORD4WzzwS2X70zceRjo
    Hash32: X7qELoC81uYOURHASH32vQaQNT54PSmcNR_8

Crucial Mapping:

  • PrivateKey goes into the server config (realitySettings.privateKey) .
  • Password is actually your Public Key. This goes into the client config as PublicKey .
  • Hash32 is ignored (it is just for verification).

6.3 Prepare UUID and shortId

VLESS requires a UUID for client identity, and REALITY requires a shortId.

  • generate a UUID :
    shell
    /usr/local/bin/xray uuid
    (Save this for both server and client configs).
  • generate a shortId (an 8-16 character hex string) :
    shell
    openssl rand -hex 8
    (Example output: 2113SHORTID046. Save this for both server and client configs).

6.4 The Minimal Inbound Configuration

We will completely replace the default /usr/local/etc/xray/config.json with a clean configuration .

Note: At this stage, dest points to www.curvature.blog:443 because Nginx is still occupying port 443. We will change this later when we implement port-sharing .

  • edit the config:
    shell
    sudo vim /usr/local/etc/xray/config.json
  • insert the following JSON, replacing the dummy values with your generated UUID, PrivateKey, and shortId :
    JSON
    {
      "log": {
        "loglevel": "info",
        "access": "/var/log/xray/access.log",
        "error": "/var/log/xray/error.log"
      },
      "inbounds": [
        {
          "tag": "vless-reality-vision",
          "listen": "0.0.0.0",
          "port": 4433,
          "protocol": "vless",
          "settings": {
            "clients": [
              {
                "id": "YOUR-UUID-HERE",
                "flow": "xtls-rprx-vision"
              }
            ],
            "decryption": "none"
          },
          "streamSettings": {
            "network": "tcp",
            "security": "reality",
            "realitySettings": {
              "show": false,
              "dest": "www.curvature.blog:443",
              "xver": 0,
              "serverNames": [
                "www.curvature.blog"
              ],
              "privateKey": "YOUR-REALITY-PRIVATE-KEY-HERE",
              "shortIds": [
                "YOUR-SHORT-ID-HERE"
              ]
            }
          }
        }
      ],
      "outbounds": [
        {
          "tag": "direct",
          "protocol": "freedom",
          "settings": {}
        },
        {
          "tag": "blocked",
          "protocol": "blackhole",
          "settings": {}
        }
      ]
    }

6.5 Restart and confirm listening

After editing the configuration, apply it and verify.

  • restart service:
    shell
    sudo systemctl restart xray
    sudo systemctl status xray
  • confirm it is listening on port 4433 :
    shell
    sudo ss -tulpn | grep xray

6.6 Client setup (Android V2rayNG)

On Android, create a new profile with the following parameters :

Basics:

  • Type/Protocol: VLESS
  • Address/Host: www.curvature.blog
  • Port: 4433
  • UUID: (must match server)
  • Encryption: none
  • Network/Transport: tcp
  • Flow: xtls-rprx-vision

Security / REALITY section:

  • Security: reality
  • SNI / ServerName: www.curvature.blog
  • PublicKey: (Use the Password output from xray x25519)
  • ShortId: (must match server)
  • Fingerprint: chrome (or another common browser)
  • AllowInsecure: false or off
  • SpiderX: (leave empty or /)
  • Mux: off (keep off for simpler debugging)

6.7 How we verified it works

Server side :

  • watch access log:
    shell
    sudo tail -f /var/log/xray/access.log

Client side :

  • With V2rayNG turned ON (VPN key icon visible), visit an IP-check website (like https://ifconfig.me). It should show your VPS public IP, not your home/cellular IP.
  • As you browse, new lines should appear in the server's access.log.

6.8 Common problems we encountered (and fix)

  • Problem: V2rayNG shows error io: read/write on closed pipe, and browser shows "DNS problem" .
  • Accompanying Server Error: /var/log/xray/error.log shows REALITY: processed invalid connection ... authentication failed or validation criteria not met .
    • Cause: The client reached the server on 4433, but the REALITY handshake failed due to a parameter mismatch .
    • Fix: Strictly double-check that the UUID, shortId, and serverName match exactly. Ensure you put the PrivateKey on the server and the Password (PublicKey) on the client .
  • Problem: Server access.log shows nothing .
    • Cause: Client never reached server (port blocked, DNS wrong, service not listening) or handshake failed instantly.
    • Fix: Confirm listening with ss -tuln, check UFW firewall rules, and monitor /var/log/xray/error.log .

6.9 Quick checklist for this stage

After finishing this section, you should have:

  • Xray inbound running actively on dedicated port 4433.
  • A syntactically correct config.json with REALITY parameters filled out.
  • Android V2rayNG configured and connecting successfully.
  • Server access.log showing accepted connections.
  • Browsing through the VPN works and successfully masks your local IP.