Xray on a Linux VPS (VLESS + REALITY)

Minimal setup guide for running Xray on a VPS and connecting from a client app.

What this guide includes

Prerequisites


1) Provision a cloud VPS

  1. Choose a cloud provider and region close to your users.
  2. Create a virtual machine with Ubuntu LTS.
  3. For up to four users, a practical starting point is 1 shared vCPU and 1 GB RAM; 2 GB RAM gives more headroom.
  4. Add SSH key authentication and disable password login.
  5. Note the instance's public IPv4 address and, if available, its IPv6 address and gateway.

Initial server hardening:

sudo apt update && sudo apt upgrade -y

2) Enable IPv6 on your VPS

Some users' internet service providers (ISPs) may block IPv4 while allowing IPv6. Enable IPv6 in your cloud provider's control panel and verify the assigned IPv6 address and gateway.

ip -6 addr show
ip -6 route

Only use the Netplan example if your cloud provider does not configure IPv6 automatically. Do not apply it unchanged; use the exact values from the provider's network settings or the VPS:

The prefix length (the number after /) is provider-specific; use the assigned value rather than assuming /24 or /64.

Example Netplan config:

network:
  version: 2
  ethernets:
    YOUR_INTERFACE_NAME:
      match:
        macaddress: "YOUR_MAC_ADDRESS"
      addresses:
      - "YOUR_IPV4/CIDR"
      - "YOUR_IPV6/CIDR"
      nameservers:
        addresses:
        - "YOUR_DNS_IPV4_1"
        - "YOUR_DNS_IPV4_2"
        search: []
      routes:
      - to: "0.0.0.0/0"
        via: "YOUR_IPV4_GATEWAY"
      - to: "::/0"
        via: "YOUR_IPV6_GATEWAY"

Apply the config:

sudo netplan apply

3) Generate Xray connection values

Two separate pieces are involved: config.json is Xray's server-side configuration file, while a VLESS URL contains the connection details imported into a client app. The VLESS URL uses the REALITY public key, never the private key.

The xray x25519 command prints the matching private and public keys. Some Xray versions label the client public-key value Password; use that value as pbk, not the private key. openssl rand -hex 8 generates one 16-character short ID each time it runs.

# Generate a UUID per user
cat /proc/sys/kernel/random/uuid

# Generate REALITY key pair
/usr/local/bin/xray x25519

# Generate a short ID
openssl rand -hex 8

4) Configure and start Xray

The command below writes the entire example to Xray's server config file, /usr/local/etc/xray/config.json, replacing that file if it already exists. Back up an existing config before replacing it; if you already have a working config, edit that file instead.

realitySettings is not a separate file. It is a section inside config.json that configures REALITY for this inbound connection. In this example:

The 0.0.0.0 listen value means “listen on all IPv4 network interfaces”; it is not your VPS's public IP. The 0.0.0.0/0 and ::/0 values in the Netplan example mean “the default route.” Leave these special values unchanged.

This sample listens on IPv4 only. To also accept IPv6 connections, change the inbound listen value from 0.0.0.0 to ::.

Replace the YOUR_... placeholders with your values, but keep the JSON quotes, commas, and braces in place.

sudo tee /usr/local/etc/xray/config.json << 'EOF'
{
  "log": { "loglevel": "warning" },
  "inbounds": [
    {
      "listen": "0.0.0.0",
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [
          { "id": "YOUR_UUID_USER1", "flow": "xtls-rprx-vision" },
          { "id": "YOUR_UUID_USER2", "flow": "xtls-rprx-vision" }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": "YOUR_SNI_HOST:443",
          "xver": 0,
          "serverNames": ["YOUR_SNI_HOST"],
          "privateKey": "YOUR_PRIVATE_KEY",
          "shortIds": ["YOUR_SHORT_ID"]
        }
      }
    }
  ],
  "outbounds": [{ "protocol": "freedom" }]
}
EOF

Validate and restart:

sudo /usr/local/bin/xray -test -config /usr/local/etc/xray/config.json
sudo systemctl restart xray
sudo systemctl status xray --no-pager

Before using an IPv6 address in a VLESS URL, confirm that Xray is listening for IPv6 connections. Run sudo ss -lntp 'sport = :443'. Look for LISTEN and [::]:443 to confirm an IPv6 listener; 0.0.0.0:443 indicates an IPv4-only listener. This checks the VPS itself, not the cloud firewall. The cloud firewall must separately allow inbound TCP 443 over IPv6.


5) Firewall setup

Allow inbound TCP 443 for Xray in the cloud provider firewall and server firewall, if both are enabled.

sudo ufw allow 443/tcp
sudo ufw reload

6) Client VLESS URL format

vless://YOUR_UUID@YOUR_SERVER_ADDRESS:443?encryption=none&security=reality&sni=YOUR_SNI_HOST&fp=chrome&pbk=YOUR_PUBLIC_KEY&sid=YOUR_SHORT_ID&flow=xtls-rprx-vision#YOUR_LABEL

Replace the placeholders as follows:

Each user's VLESS URL contains their UUID, which acts as a credential. Share each URL privately with its intended user. The server's REALITY private key belongs only in config.json, never in a VLESS URL.


7) Adding new users

Generate a new UUID and add it to the clients list in config.json. Test the config and restart Xray, then create a VLESS URL with that UUID and share it privately with the user.


8) Monitoring and troubleshooting

Use these commands one at a time to check whether Xray is running and inspect its logs if a client cannot connect:

sudo systemctl status xray --no-pager
sudo journalctl -u xray -n 100 --no-pager -l
sudo journalctl -u xray -f

Notes