REALITY is a modified form of TLS in Xray that borrows the handshake of a real website, so your connection looks like ordinary browsing to that site, with no domain or certificate of your own. This guide builds it from the Xray project's own documentation, on a VPS set up as in the first ten minutes on a new VPS.
1. Install Xray
The official install script works on any systemd distro. It puts the config at /usr/local/etc/xray/config.json and starts the service right away with an empty config, which is why step 4 restarts it.
$ sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install $ xray version
This guide was tested with Xray v26.3.27. Run the same command later to update.
2. Generate the three secrets
$ xray uuid $ xray x25519 $ openssl rand -hex 8
xray x25519 prints a PrivateKey for the server config and a Password (PublicKey) for the client; you can ignore Hash32. The random hex is your short ID. A short ID needs an even number of characters, up to 16.
3. Pick a site to imitate
REALITY borrows the handshake of a real site, called the target. Pick one that:
- answers with TLS 1.3 and HTTP/2
- does not redirect, which often means using the
wwwhostname - is not behind a CDN such as Cloudflare, and ideally sits in the same ASN as your VPS
- is not an Apple or iCloud hostname, which Xray warns tend to get server IPs blocked
Check a candidate:
$ xray tls ping www.example.com $ curl -sSI --http2 https://www.example.com/
You want TLS 1.3 from the first command, and an HTTP/2 status with no Location: header from the second. A via:, x-cache: or cf-ray: header, or server: cloudflare, means a CDN is in front of the site.
4. Write the server config
{
"log": { "loglevel": "warning" },
"inbounds": [
{
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{ "id": "YOUR-UUID", "flow": "xtls-rprx-vision" }
],
"decryption": "none"
},
"streamSettings": {
"network": "raw",
"security": "reality",
"realitySettings": {
"target": "www.example.com:443",
"serverNames": ["www.example.com"],
"privateKey": "YOUR-PRIVATE-KEY",
"shortIds": ["YOUR-SHORT-ID"]
}
}
}
],
"routing": {
"domainStrategy": "IPIfNonMatch",
"rules": [
{ "ip": ["geoip:private"], "outboundTag": "block" }
]
},
"outbounds": [
{ "protocol": "freedom", "tag": "direct" },
{ "protocol": "blackhole", "tag": "block" }
]
}Put your target's hostname in target (with :443) and in serverNames, without wildcards. Older guides call target by its old name, dest; both work. The routing block stops anyone using your link from reaching services on the server itself or its private network, such as a database bound to 127.0.0.1.
Test the file, then restart the service:
$ sudo xray run -test -config /usr/local/etc/xray/config.json $ sudo systemctl restart xray $ sudo systemctl status xray
The test should print Configuration OK. Use restart here. The service is already running the empty config from step 1, and only a restart loads yours. Restart again after every config change.
5. Open port 443 in your firewall
Xray does not need a firewall, but if you run one, open 443 in it. If you run neither ufw nor firewalld, skip this step.
$ sudo ufw allow 443/tcp$ sudo firewall-cmd --get-active-zones $ zone=public $ sudo firewall-cmd --permanent --zone="$zone" --add-port=443/tcp $ sudo firewall-cmd --reload
For firewalld, replace public with the zone the first command shows.
6. Give the client its link
Build the link from your values and import it into a client such as v2rayN on Windows or v2rayNG on Android:
vless://[email protected]:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=www.example.com&fp=chrome&pbk=YOUR-PUBLIC-KEY&sid=YOUR-SHORT-ID&type=tcp#MyVPS
pbk is the public key from step 2 and sid is the short ID.
Treat the link like a password
It holds everything needed to use your server. Do not post it publicly.
7. When it does not connect
$ sudo systemctl status xray $ sudo ss -lntp | grep ':443'
- If nothing is listening on 443, restart Xray and read the log with
sudo journalctl -u xray -n 50. - If the client log says
received real certificate, thepbk,sidor SNI in the link does not match the server config. - If the target no longer passes the step 3 checks, pick another one.
- For more detail, set
logleveltoinfo, restart Xray, retry once, and read the server log. Set it back afterwards.
Quick reference
| Task | Command |
|---|---|
| Install or update | sudo bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install |
| New UUID | xray uuid |
| New key pair | xray x25519 |
| Client password (public key) from a private key | xray x25519 -i "PRIVATE-KEY" |
| New short ID | openssl rand -hex 8 |
| Check a target site | xray tls ping HOST and curl -sSI --http2 https://HOST/ |
| Test the config | sudo xray run -test -config /usr/local/etc/xray/config.json |
| Apply a config change | sudo systemctl restart xray |
| Logs | sudo journalctl -u xray -n 50 |
Where to go next
REALITY hides what the traffic is. Observers still see that you connect to your server, and no proxy is guaranteed to stay reachable on every network. It also cannot fix distance or poor routing, so before you order, use the Looking Glass to ping each location's test address from your own network. Keep SSH key-only and fail2ban running as usual.