Setting Up VLESS + Reality: A Censorship-Resistant Proxy
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:
- Traffic looks identical to legitimate HTTPS connections
- Uses actual TLS fingerprints from major websites
- No domain or certificate management required
- TCP-based (avoids UDP throttling)
- Fast and stable for daily use
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:
- A VPS with root access (Hetzner, DigitalOcean, or similar)
- Ubuntu 22.04 LTS (recommended)
- SSH access to your server
- A client app (Shadowrocket, v2rayN, or similar)
Recommended server specifications:
- 2+ CPU cores
- 2+ GB RAM
- Located in a privacy-friendly region (Finland, Netherlands, Germany)
Initial Server Setup#
Connect to your server and update the system:
ssh root@your_server_ip
apt update && apt upgrade -y
Create a Non-Root User (Recommended)#
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:
- Binary at
/usr/local/bin/xray - Config directory at
/usr/local/etc/xray/ - Systemd service
xray.service
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": "[email protected]"
}
],
"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
port: 443— Standard HTTPS port, blends with normal trafficprotocol: "vless"— Lightweight, modern proxy protocolflow: "xtls-rprx-vision"— Reality-specific flow control
realitySettings: The stealth layer
dest— Website whose TLS fingerprint to borrowserverNames— Domains clients claim to visitprivateKey— Your server’s secret keyshortIds— Authentication tokens
outbounds: Where traffic exits
protocol: "freedom"— Direct connection to the internet
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:
| Parameter | Value |
|---|---|
| Address | Your server IP |
| Port | 443 |
| UUID | Your generated UUID |
| Flow | xtls-rprx-vision |
| Security | reality |
| SNI | www.microsoft.com |
| Fingerprint | chrome |
| Public Key | Your public key |
| Short ID | Your short ID |
| Network | tcp |
Popular Client Apps#
- Windows: v2rayN, Nekoray
- macOS: V2RayXS, FoXray
- Android: v2rayNG, NekoBox
- iOS: Shadowrocket, FoXray
- Linux: Nekoray, v2rayA
Shadowrocket Configuration (iOS)#
- Tap + to add a new server
- Select Type → VLESS
- Fill in Address, Port, UUID
- Enable TLS
- Set Security to reality
- Enter SNI, Public Key, Short ID
- Set XTLS to xtls-rprx-vision
- 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": "[email protected]"
},
{
"id": "UUID-FOR-USER-2",
"flow": "xtls-rprx-vision",
"email": "[email protected]"
}
]
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>>>[email protected]>>>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:
- CDN providers:
www.cloudflare.com - Major tech companies:
www.microsoft.com,www.apple.com - Popular services:
addons.mozilla.org
Multiple Short IDs#
Assign different short IDs to different users or devices:
"shortIds": [
"a1b2c3d4e5f6g7h8",
"9876543210abcdef",
"028f3f4a7b6c9d8e"
]
Benefits:
- Revoke individual access without affecting others
- Track which device/user connected
- Identify leaks by seeing which ID was compromised
Key Rotation#
Every 3-6 months:
- Generate new short IDs
- Update server configuration
- Distribute new credentials to users
- Remove old short IDs after a grace period
Troubleshooting#
Common Issues#
Connection timeout
- Check if Xray is running:
sudo systemctl status xray - Verify port is listening:
sudo ss -tulpn | grep 443 - Check firewall rules:
sudo ufw status
Permission denied on port 443
- Apply capability:
sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/xray - Restart service:
sudo systemctl restart xray
Configuration errors
- Test syntax:
sudo xray run -test -config /usr/local/etc/xray/config.json - Check logs:
sudo journalctl -u xray -n 50
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:
- Keep your private key and UUIDs secure
- Use multiple serverNames and shortIds for better security
- Monitor traffic to detect unusual patterns
- Update Xray regularly for security fixes
- Always test configuration changes before restarting