Hands-On: Deploying VLESS + Reality from Scratch
Chapters
In the previous chapter, we dissected how Reality works under the hood — the uTLS fingerprinting, the Curve25519 key exchange, and the clever fallback mechanism that makes your server indistinguishable from a legitimate website. Now it's time to get our hands dirty and build one from scratch.
By the end of this chapter, you'll have a fully functional Reality node that even active probing can't identify.
Why This Scheme Stands Out
Reality is the lowest-friction, highest-security deployment option in the Xray ecosystem:
- No domain required — works with a bare IP address
- No TLS certificate — no Let's Encrypt, no renewal headaches
- No reverse proxy — Xray listens directly on port 443, no Nginx needed
- Strongest anti-detection — active probers see a real, legitimate website
- Minimal configuration — a single JSON file on the server side
If you want a node running in under 10 minutes with maximum stealth, this is it.
Prerequisites
| Requirement | Details |
|---|---|
| VPS | A server with a public IP address (no domain needed) |
| Port 443 | Not occupied by another service |
| SSH access | Root or sudo login to the server |
If your VPS already has Nginx listening on port 443 (for example, running the WS + CDN scheme from Chapter 3), you have two options: deploy Reality on an alternative port like 8443, or use a separate VPS. Running both schemes on the same machine is covered in the advanced section at the end of this chapter.
Installing Xray
Run the official installation script:
Verify the installation:
You should see version output like Xray 25.x.x. If the command isn't found, check your PATH or re-run the install script.
Generating Authentication Parameters
Reality requires three sets of credentials: a UUID, an X25519 key pair, and a Short ID.
UUID
Example output: a1b2c3d4-e5f6-7890-abcd-ef1234567890
X25519 Key Pair
Example output:
Private Key goes on the server. Public Key goes to the client. Mixing these up is the single most common configuration mistake — and it produces cryptic connection failures with no helpful error message.
Short ID
Example output: a1b2c3d4e5f6a7b8
The Short ID is a 1–16 character hex string that acts as an additional authentication layer. Even if your key pair leaks, an attacker cannot connect without the correct Short ID.
Summary of Generated Values
Save these somewhere safe — you'll need them in the next steps:
Choosing a Disguise Target
Reality needs a "fallback destination" — when someone probes your server without valid credentials, Xray forwards the request to this target and returns its real content. The probe sees a genuine website, not your proxy.
Selection Criteria
| Criterion | Why It Matters |
|---|---|
| Must support TLS 1.3 | Reality depends on TLS 1.3 features |
| Should support HTTP/2 | Better performance and blending |
| High global traffic volume | Your traffic hides in the noise |
| Not blocked by the censor | Don't use google.com |
| Global CDN deployment | Reasonable latency from any geo |
Recommended Targets
Verifying Your Target
Run this from your VPS:
You should see HTTP/2 200 in the response. If you get an error or only HTTP/1.1, pick a different target.
Bad Choices
| Target | Problem |
|---|---|
google.com / youtube.com | Blocked in China — falling back to them exposes you |
addons.mozilla.org | Overused in early Reality deployments, now flagged |
| Low-traffic niche sites | Your connections become statistically conspicuous |
| Domains that 301 redirect | Redirect behavior leaks anomalies |
Configuring the Xray Server
Open the configuration file:
Paste the following, replacing the five placeholder values:
Parameter Reference
| Parameter | Value | Purpose |
|---|---|---|
listen | 0.0.0.0 | Listen on all network interfaces |
port | 443 | Standard HTTPS port — blends naturally with normal traffic |
flow | xtls-rprx-vision | XTLS Vision flow control, eliminates TLS-in-TLS fingerprint |
decryption | none | VLESS protocol does not add an extra encryption layer |
network | tcp | Reality requires TCP (WebSocket/gRPC are incompatible) |
security | reality | Enables the Reality protocol |
dest | www.microsoft.com:443 | Fallback target — unauthorized requests are forwarded here |
serverNames | ["www.microsoft.com"] | SNI values the client is allowed to present |
privateKey | Your private key | Server-side private key (generated earlier) |
shortIds | ["xxx", ""] | Allowed Short IDs — "" means empty is also accepted |
sniffing | enabled | Inspects traffic type for correct routing decisions |
The empty string "" in the shortIds array means clients can connect without providing a Short ID. For stricter security, remove the empty string so that only clients with the correct Short ID can authenticate.
Validate Configuration Syntax
Expected output: Configuration OK. If you see a JSON syntax error, check for missing commas, mismatched quotes, or stray brackets.
Starting and Verifying Xray
Confirm you see Active: active (running) in the output.
Confirming Port Binding
Expected output:
If the port isn't listening:
- Check if another service is occupying 443 (e.g., Nginx):
ss -tlnp | grep 443 - Check Xray logs for startup errors:
journalctl -u xray -n 20
Firewall Configuration
Ensure port 443 is open to the internet:
Don't forget the cloud provider's security group! AWS, GCP, Azure, DigitalOcean, and others all have their own network-level firewalls independent of your OS. Even if ufw shows port 443 as allowed, the connection will fail if the security group blocks it. Go to your cloud console → Security Groups → Inbound Rules → Allow TCP 443.
Verifying Reality Fallback
Run these tests from your local machine (not the VPS):
If this returns Microsoft's HTML page content, your Reality fallback is working correctly. Any prober connecting to your port 443 will see a legitimate Microsoft website.
This should return an error or time out — without a valid SNI, Reality rejects the connection. This is expected and correct behavior.
Client Configurations
Clash Meta and mihomo
The client-fingerprint field is mandatory! Reality requires the client to present a browser TLS fingerprint. Use chrome (most common), or alternatives like firefox, safari, or edge. Omitting this field will cause connection failures.
Remember to add the node name to the appropriate proxy-groups section in your config.
v2rayN for Windows
Method 1: Import via share link (recommended)
Copy the link → v2rayN → Servers → Import bulk URLs from clipboard.
Method 2: Manual configuration
| Field | Value |
|---|---|
| Address | YOUR_VPS_IP |
| Port | 443 |
| User ID | Your UUID |
| Flow | xtls-rprx-vision |
| Encryption | none |
| Transport | tcp |
| Security | reality |
| SNI | www.microsoft.com |
| Fingerprint | chrome |
| PublicKey | Your Public Key |
| ShortId | Your Short ID |
v2rayNG for Android
Identical to v2rayN — import the same share link directly. When adding manually, use exactly the same parameters as the table above.
sing-box (Cross-Platform)
Shadowrocket for iOS
| Field | Value |
|---|---|
| Protocol | VLESS |
| Address | YOUR_VPS_IP |
| Port | 443 |
| UUID | Your UUID |
| Flow | xtls-rprx-vision |
| TLS | On |
| Reality | On |
| Public Key | Your Public Key |
| Short ID | Your Short ID |
| Server Name | www.microsoft.com |
| Fingerprint | chrome |
Testing the Connection
Once your client connects to the node:
If you see your VPS IP address, congratulations — deployment is complete.
Troubleshooting
Connection Timeout
| Check | Command | Expected |
|---|---|---|
| Xray running? | systemctl status xray | active (running) |
| Port listening? | ss -tlnp | grep 443 | Shows xray process |
| OS firewall? | ufw status | 443 is ALLOW |
| Cloud security group? | Check cloud console | Inbound TCP 443 allowed |
TLS Handshake Failure
Cause: The client's servername (SNI) doesn't match any entry in the server's serverNames array.
Fix: Ensure the SNI in your client config exactly matches a value in the server's serverNames list.
Connection Rejected or No Internet After Connecting
| Cause | Solution |
|---|---|
| UUID mismatch | Compare server clients.id with client UUID |
| Short ID mismatch | Compare server shortIds with client short-id |
| Keys are swapped | Server must have Private Key, client must have Public Key |
flow not set | Client must specify flow: xtls-rprx-vision |
sniffing disabled | Add the sniffing block to server config |
Flow Error — Stream Is Not TCP
Cause: Reality with Vision flow only works over TCP. It's incompatible with WebSocket, gRPC, or mux (multiplexing).
Fix: Ensure network is set to tcp and mux/multiplex is disabled in your client.
Xray Startup Errors
Check the logs for details:
| Log Keyword | Cause | Solution |
|---|---|---|
invalid private key | Malformed private key | Regenerate with xray x25519 |
address already in use | Port 443 is occupied | Stop the conflicting service or use another port |
invalid JSON | Config file syntax error | Validate commas, quotes, and brackets |
failed to dial | Cannot reach fallback target | Verify the dest domain resolves from your VPS |
Slow Speeds
Reality itself is extremely performant — it's raw TCP with no proxy overhead beyond encryption. If speeds are poor, the issue is almost always the network path:
- Benchmark raw throughput:
curl -o /dev/null -w "%{speed_download}" https://speed.cloudflare.com/__down?bytes=10000000 - Optimize routing: Consider adding Cloudflare WARP as an outbound to improve exit routing
- Time of day matters: International links are congested during peak hours (20:00–23:00 local time in China)
Advanced: Multi-User Configuration
Generate a separate UUID for each user:
Add multiple entries to the clients array:
The email field isn't a real email address — it's just a label for identifying traffic sources in the logs. All users share the same key pair and Short ID.
Advanced: Non-443 Port Deployment
If port 443 is already occupied by Nginx (for instance, running the CDN scheme from Chapters 3–4), use an alternative port:
Update your client configurations to connect to port 8443 accordingly.
When using a non-443 port, Xray will log a warning: REALITY: listening on a non-443 port. The real-world risk depends on your environment — most overseas VPS providers won't flag non-standard ports. That said, port 443 remains the most natural and stealthy choice since it mimics normal HTTPS traffic.
Advanced: Running Reality and XHTTP on the Same VPS
If you want both Reality (direct backup) and XHTTP + CDN (daily driver) on a single machine, here's the architecture:
Configure Xray with multiple inbounds:
The two inbounds can share a single UUID or use separate ones for independent management. Reality listens publicly on 8443, while the XHTTP inbound only accepts local connections from Nginx on port 10001.
This dual-inbound approach gives you the best of both worlds: CDN protection for your IP address as the everyday connection, and Reality's raw TCP performance as a fast backup when CDN routing is slow or unreachable.
Common Maintenance Commands
Wrapping Up the Series
With this chapter complete, you now have the full VLESS + Reality deployment working — from parameter generation to client connection, with no domain or certificate dependencies.
Looking back across the entire series, you've built a robust two-scheme strategy:
| Role | Scheme | Strength |
|---|---|---|
| Primary | XHTTP + CDN (Chapters 3–4) | IP protection, survives IP bans, blends with legitimate CDN traffic |
| Backup | VLESS + Reality (Chapters 5–6) | Raw performance, zero domain dependency, strongest active probe resistance |
The CDN scheme hides your server's real IP and gives you resilience — if the IP gets blocked, just change the CDN-facing origin. The Reality scheme gives you maximum speed with minimal infrastructure, and its probe resistance means even direct connections to your IP appear legitimate.
Run both on the same VPS or across two machines. Switch between them as conditions change. Together, they form a complete, production-grade censorship circumvention setup that handles every realistic threat model.