Andrey's Blog

Setting Up VLESS + Reality

VLESS with Reality is currently one of the strongest setups for bypassing network censorship. Unlike traditional VPNs that have detectable signatures, Reality makes your traffic indistinguishable from normal HTTPS connections to major websites.

This guide walks through the complete setup process: from server installation to client configuration, multi-user management, and traffic monitoring.

Why VLESS + Reality?

Reality solves a fundamental problem with proxy detection. Traditional approaches use synthetic TLS certificates that deep packet inspection (DPI) systems can fingerprint. Reality instead “borrows” the TLS identity of real websites like Microsoft or Cloudflare.

Key advantages:

When a censor inspects your connection, they see what appears to be a normal visit to microsoft.com. Only clients with the correct cryptographic keys can actually connect to your server.

Prerequisites

Before starting, you’ll need:

Recommended server specifications:

Initial Server Setup

Connect to your server and update the system:

ssh root@your_server_ip
apt update && apt upgrade -y

Running services as root is a security risk. Create a dedicated user:

adduser xray-admin
usermod -aG sudo xray-admin

Set up SSH key authentication for the new user:

mkdir -p /home/xray-admin/.ssh
cp ~/.ssh/authorized_keys /home/xray-admin/.ssh/
chown -R xray-admin:xray-admin /home/xray-admin/.ssh
chmod 700 /home/xray-admin/.ssh
chmod 600 /home/xray-admin/.ssh/authorized_keys

Installing Xray-core

Install Xray using the official script:

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

This creates:

Verify the installation:

xray version

Generating Keys

Reality requires three types of credentials: a key pair, a UUID, and short IDs.

Generate the Key Pair

xray x25519

Output:

Private key: eLJwm7VGlO4pVxxxxxxxxxxxxxQ8C_uMVxxxxxxxx
Public key: nO8BvFqxxxxxxxxxxxxx-2MxxxxxxxxxxxxxY

Save both keys securely. The private key stays on the server; the public key goes to clients.

Generate a UUID

xray uuid

This creates a unique identifier for client authentication.

Generate Short IDs

openssl rand -hex 8

Short IDs act as additional authentication tokens. Generate multiple for different users or devices.

Server Configuration

Create the Xray configuration file:

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

Paste the following configuration:

{
  "log": {
    "loglevel": "warning"
  },
  "inbounds": [
    {
      "port": 443,
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "id": "YOUR-UUID-HERE",
            "flow": "xtls-rprx-vision",
            "email": "user1@example.com"
          }
        ],
        "decryption": "none"
      },
      "streamSettings": {
        "network": "tcp",
        "security": "reality",
        "realitySettings": {
          "show": false,
          "dest": "www.microsoft.com:443",
          "xver": 0,
          "serverNames": [
            "www.microsoft.com"
          ],
          "privateKey": "YOUR-PRIVATE-KEY-HERE",
          "shortIds": [
            "YOUR-SHORT-ID-HERE"
          ]
        }
      },
      "sniffing": {
        "enabled": true,
        "destOverride": ["http", "tls"]
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "direct"
    }
  ]
}

Replace the placeholder values with your generated credentials.

Configuration Explained

inbounds: How connections enter your server

realitySettings: The stealth layer

outbounds: Where traffic exits

Firewall Configuration

Configure UFW to allow only essential ports:

# Allow SSH first (critical!)
sudo ufw allow 22/tcp

# Allow Xray
sudo ufw allow 443/tcp

# Enable firewall
sudo ufw enable

# Verify rules
sudo ufw status verbose

Starting the Service

Fix Port Binding Permissions

Port 443 is privileged. Grant Xray permission to use it:

sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/xray

Enable and Start Xray

sudo systemctl enable xray
sudo systemctl start xray
sudo systemctl status xray

Verify the port is listening:

sudo ss -tulpn | grep 443

Expected output:

tcp   LISTEN   0   128   *:443   *:*   users:(("xray",pid=1234,fd=3))

Test Configuration Syntax

Before restarting after config changes:

sudo xray run -test -config /usr/local/etc/xray/config.json

Performance Optimization

Enable BBR congestion control for better throughput:

echo "net.core.default_qdisc=fq" | sudo tee -a /etc/sysctl.conf
echo "net.ipv4.tcp_congestion_control=bbr" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

Verify BBR is active:

sysctl net.ipv4.tcp_congestion_control

BBR significantly improves speed on high-latency or lossy networks.

Client Configuration

Each client needs these parameters:

ParameterValue
AddressYour server IP
Port443
UUIDYour generated UUID
Flowxtls-rprx-vision
Securityreality
SNIwww.microsoft.com
Fingerprintchrome
Public KeyYour public key
Short IDYour short ID
Networktcp

Shadowrocket Configuration (iOS)

  1. Tap + to add a new server
  2. Select TypeVLESS
  3. Fill in Address, Port, UUID
  4. Enable TLS
  5. Set Security to reality
  6. Enter SNI, Public Key, Short ID
  7. Set XTLS to xtls-rprx-vision
  8. Save and connect

Testing the Connection

After connecting, verify your IP has changed:

https://ifconfig.me

The displayed IP should match your server’s IP.

Adding Multiple Users

Each user needs a unique UUID. They share the same server settings.

Generate Additional UUIDs

xray uuid

Update Configuration

Edit the clients array:

"clients": [
  {
    "id": "UUID-FOR-USER-1",
    "flow": "xtls-rprx-vision",
    "email": "user1@example.com"
  },
  {
    "id": "UUID-FOR-USER-2",
    "flow": "xtls-rprx-vision",
    "email": "user2@example.com"
  }
]

The email field is optional but helps identify users in logs.

Restart Xray after changes:

sudo systemctl restart xray

Traffic Monitoring

Enable statistics to track per-user traffic.

Update Configuration for Stats

Add these sections to your config:

{
  "stats": {},
  "api": {
    "tag": "api",
    "services": ["StatsService"]
  },
  "policy": {
    "levels": {
      "0": {
        "statsUserUplink": true,
        "statsUserDownlink": true
      }
    },
    "system": {
      "statsInboundUplink": true,
      "statsInboundDownlink": true
    }
  },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 10085,
      "protocol": "dokodemo-door",
      "settings": {
        "address": "127.0.0.1"
      },
      "tag": "api"
    }
  ],
  "routing": {
    "rules": [
      {
        "inboundTag": ["api"],
        "outboundTag": "api",
        "type": "field"
      }
    ]
  }
}

Query Traffic Statistics

xray api statsquery --server=127.0.0.1:10085

Query specific user:

xray api statsquery --server=127.0.0.1:10085 \
  --pattern "user>>>user1@example.com>>>traffic>>>downlink"

Traffic Monitoring Script

Create a script to display formatted statistics:

#!/bin/bash

# Add color output
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

echo -e "${BLUE}=== Xray Traffic Statistics ===${NC}"
echo ""
echo "Timestamp: $(date)"
echo "Server uptime: $(uptime -p)"
echo ""

# Check if Xray is running
if ! systemctl is-active --quiet xray; then
    echo -e "${RED}❌ Xray service is not running!${NC}"
    exit 1
fi

# Function to convert bytes to human readable
bytes_to_human() {
    local bytes=$1
    if [ -z "$bytes" ] || [ "$bytes" = "0" ]; then
        echo "0 B"
    else
        numfmt --to=iec-i --suffix=B "$bytes" 2>/dev/null || echo "$bytes bytes"
    fi
}

# Get all user stats
stats_output=$(xray api statsquery --server=127.0.0.1:10085 2>/dev/null)

if [ $? -ne 0 ]; then
    echo "❌ Error: Cannot connect to Xray API"
    echo "Make sure Xray is running: sudo systemctl status xray"
    exit 1
fi

# Extract unique user emails
emails=$(echo "$stats_output" | grep -oP 'user>>>.*?>>>traffic' | cut -d'>' -f4 | sort -u)

if [ -z "$emails" ]; then
    echo "⚠️  No users found. This could mean:"
    echo "   - No traffic has been generated yet"
    echo "   - API is not configured correctly"
    exit 0
fi

# Loop through each user
for email in $emails; do
    # Get uplink
    uplink=$(xray api statsquery --server=127.0.0.1:10085 --pattern "user>>>${email}>>>traffic>>>uplink" 2>/dev/null | grep -oP '"value":\s*\K[0-9]+' || echo "0")

    # Get downlink
    downlink=$(xray api statsquery --server=127.0.0.1:10085 --pattern "user>>>${email}>>>traffic>>>downlink" 2>/dev/null | grep -oP '"value":\s*\K[0-9]+' || echo "0")

    # Calculate total
    total=$((uplink + downlink))

    echo -e "${GREEN}🌐 User: $email${NC}"
    echo "   ⬆️  Upload:   $(bytes_to_human $uplink)"
    echo "   ⬇️  Download: $(bytes_to_human $downlink)"
    echo "   📊 Total:    $(bytes_to_human $total)"
    echo ""
done

echo "---"
echo "💡 Tip: Run 'sudo systemctl restart xray' to reset counters"

Security Enhancements

Multiple Server Names

Using multiple serverNames makes your traffic pattern more diverse:

"serverNames": [
  "www.microsoft.com",
  "www.cloudflare.com",
  "addons.mozilla.org"
]

Different clients can use different SNIs, making pattern detection harder.

Good choices for serverNames:

Multiple Short IDs

Assign different short IDs to different users or devices:

"shortIds": [
  "a1b2c3d4e5f6g7h8",
  "9876543210abcdef",
  "028f3f4a7b6c9d8e"
]

Benefits:

Key Rotation

Every 3-6 months:

  1. Generate new short IDs
  2. Update server configuration
  3. Distribute new credentials to users
  4. Remove old short IDs after a grace period

Troubleshooting

Common Issues

Connection timeout

Permission denied on port 443

Configuration errors

Useful Commands

# Check service status
sudo systemctl status xray

# View recent logs
sudo journalctl -u xray -n 100

# Live log monitoring
sudo journalctl -u xray -f

# Restart after config changes
sudo systemctl restart xray

# Test configuration syntax
sudo xray run -test -config /usr/local/etc/xray/config.json

Keeping Updated

Update Xray periodically for security patches:

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

Back up your configuration before updates:

sudo cp /usr/local/etc/xray/config.json ~/xray-config-backup.json

Summary

VLESS with Reality provides strong censorship resistance by making proxy traffic indistinguishable from normal HTTPS. The setup requires careful attention to key generation and configuration, but once running, it’s stable and fast.

Key points to remember: