This document explains how to set up a Reality protocol service on AWS and forward traffic through Cloudflare for better privacy protection and network performance.
textClient → Cloudflare CDN → AWS EC2 (Reality Server)
Add the following inbound rules to the EC2 security group:
| Type | Protocol | Port range | Source |
|---|---|---|---|
| SSH | TCP | 22 | Your IP |
| HTTPS | TCP | 443 | 0.0.0.0/0 |
| Custom TCP | TCP | 8443 | 0.0.0.0/0 |
bashssh -i your-key.pem ubuntu@your-ec2-ip
bashsudo apt update && sudo apt upgrade -y
bash# Download the install script bash -c "$(curl -L https://github.com/XTLS/Xray-install/raw/main/install-release.sh)" @ install # Or use the one-click install script wget -N --no-check-certificate https://raw.githubusercontent.com/teddysun/across/master/xray.sh chmod +x xray.sh sudo ./xray.sh
Create the configuration file /usr/local/etc/xray/config.json:
Important note: If you are not forwarding through Nginx, Xray needs to listen on 0.0.0.0 to accept external connections. If you are forwarding through Nginx, it can listen on 127.0.0.1.
json{ "log": { "loglevel": "warning" }, "inbounds": [ { "listen": "0.0.0.0", "port": 8443, "protocol": "vless", "settings": { "clients": [ { "id": "YOUR-UUID-HERE", "flow": "xtls-rprx-vision" } ], "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", "shortIds": [ "", "0123456789abcdef" ] } } } ], "outbounds": [ { "protocol": "freedom", "tag": "direct" } ] }
| Field | Description |
|---|---|
log | Logging configuration object |
inbounds | Array of inbound connection configurations, defining how the server accepts client connections |
outbounds | Array of outbound connection configurations, defining how the server forwards traffic |
json"log": { "loglevel": "warning" }
| Field | Possible values | Description |
|---|---|---|
loglevel | debug, info, warning, error, none | Log level. warning logs only warnings and errors |
json"inbounds": [ { "listen": "0.0.0.0", "port": 443, "protocol": "vless", "settings": { ... }, "streamSettings": { ... } } ]
| Field | Type | Description |
|---|---|---|
listen | String | Listening address. 0.0.0.0 means all network interfaces (direct connection), 127.0.0.1 means local only (Nginx forwarding) |
port | Number | Listening port. Uses 8443 (an unprivileged port), forwarded to 443 via Nginx |
protocol | String | Protocol type; Reality uses vless |
settings | Object | Protocol-specific settings |
streamSettings | Object | Transport-layer configuration |
json"settings": { "clients": [ { "id": "YOUR-UUID-HERE", "flow": "xtls-rprx-vision" } ], "decryption": "none" }
| Field | Description |
|---|---|
clients | List of clients; multiple clients can be configured |
clients[].id | Client UUID, used for authentication. Generate it with cat /proc/sys/kernel/random/uuid |
clients[].flow | Flow control mode. xtls-rprx-vision is the flow control mode recommended for Reality |
decryption | Decryption method. Reality uses none (encryption is handled by the TLS layer) |
json"streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { ... } }
| Field | Possible values | Description |
|---|---|---|
network | tcp, kcp, ws, http, grpc | Transport protocol. Reality usually uses tcp |
security | none, tls, reality | Security layer type. Use reality |
realitySettings | Object | Reality protocol-specific configuration |
json"realitySettings": { "show": false, "dest": "www.microsoft.com:443", "xver": 0, "serverNames": [ "www.microsoft.com" ], "privateKey": "YOUR-PRIVATE-KEY", "shortIds": [ "", "0123456789abcdef" ] }
| Field | Type | Description |
|---|---|---|
show | Boolean | Whether to show debug information in the logs. false is recommended in production |
dest | String | Fallback destination address. When the handshake fails, the server disguises itself as a visit to this website. Format: domain:port |
xver | Number | PROXY protocol version. 0 means unused |
serverNames | Array | SNI list; clients must use a domain from this list. Usually kept consistent with dest |
privateKey | String | Server private key, generated with /usr/local/bin/xray x25519. Must be kept secret |
shortIds | Array | List of short IDs, used to distinguish between different client configurations. Can contain an empty string and 16 hexadecimal characters |
Advice for choosing the dest field:
www.microsoft.com:443, www.apple.com:443, www.cloudflare.com:443About the shortIds field:
"" means clients are allowed to use no shortId"", "0123456789abcdef", "a1b2c3"json"outbounds": [ { "protocol": "freedom", "tag": "direct" } ]
| Field | Possible values | Description |
|---|---|---|
protocol | freedom, blackhole, vmess, vless, etc. | Outbound protocol. freedom means a direct connection |
tag | String | Identifier for the outbound connection, used by routing rules |
Commonly used outbound protocols:
freedom: connect directly to the destination address (most common)blackhole: block the connection (used for ad filtering)vmess/vless: chained proxyingbash/usr/local/bin/xray x25519
Example output:
textPrivate key: SChPg2LBZzQnvGr8aZpYmD3sLmK9qNpTrXwUyVbE1Hk Public key: xKvN7BqM3pYzWrTgLnFdKs9RmJh6VcXaQwUyE8PbN2o
privateKey field of the server's config.jsonbashcat /proc/sys/kernel/random/uuid
Example output:
texta1b2c3d4-e5f6-7890-abcd-ef1234567890
Put this UUID into the clients[].id field of config.json.
json{ "log": { "loglevel": "warning" }, "inbounds": [ { "listen": "0.0.0.0", "port": 8443, "protocol": "vless", "settings": { "clients": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "flow": "xtls-rprx-vision" } ], "decryption": "none" }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "show": false, "dest": "www.microsoft.com:443", "xver": 0, "serverNames": [ "www.microsoft.com" ], "privateKey": "SChPg2LBZzQnvGr8aZpYmD3sLmK9qNpTrXwUyVbE1Hk", "shortIds": [ "", "0123456789abcdef" ] } } } ], "outbounds": [ { "protocol": "freedom", "tag": "direct" } ] }
bashsudo systemctl start xray sudo systemctl enable xray sudo systemctl status xray
Since Xray uses the unprivileged port 8443, we need Nginx to forward traffic from port 443 to 8443.
The standard nginx package may not include the stream module; you need to install nginx-full, or check whether the module is present:
bash# First check whether Nginx is installed and which version nginx -V 2>&1 | grep -o with-stream # If there is no output, or Nginx is not installed, install the version that includes the stream module sudo apt update sudo apt install nginx-full -y # Verify again that the stream module is loaded nginx -V 2>&1 | grep -o with-stream
Expected output:
textwith-stream
If the stream module is still missing, you can install nginx-extras:
bashsudo apt remove nginx nginx-common -y sudo apt install nginx-extras -y
Option one: use a separate configuration file (recommended)
Create a separate stream configuration file to avoid modifying the main configuration:
bash# Create the stream configuration directory sudo mkdir -p /etc/nginx/streams-enabled # Create the xray forwarding configuration sudo nano /etc/nginx/streams-enabled/xray.conf
Add the following content to xray.conf:
nginxupstream xray_backend { server 127.0.0.1:8443; } server { listen 443; listen [::]:443; proxy_pass xray_backend; proxy_protocol off; }
Then add an include line at the end of the main configuration file /etc/nginx/nginx.conf (after the final }):
bashsudo nano /etc/nginx/nginx.conf
Add the following at the end of the file:
nginx# Add after the http block stream { include /etc/nginx/streams-enabled/*.conf; }
Complete nginx.conf structure example:
nginxuser www-data; worker_processes auto; pid /run/nginx.pid; events { worker_connections 768; } http { sendfile on; tcp_nopush on; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } # Include the stream configuration stream { include /etc/nginx/streams-enabled/*.conf; }
Option two: add it directly to the main configuration file (alternative)
If you would rather not create an extra directory, you can add the full stream block directly at the end of /etc/nginx/nginx.conf:
nginx# Add after the http block stream { upstream xray_backend { server 127.0.0.1:8443; } server { listen 443; listen [::]:443; proxy_pass xray_backend; proxy_protocol off; } }
bash# Test the configuration file syntax sudo nginx -t # If the test passes, restart Nginx sudo systemctl restart nginx sudo systemctl enable nginx # Check Nginx status sudo systemctl status nginx
bash# Check port 443 (should be nginx) sudo netstat -tlnp | grep 443 # Check port 8443 (should be xray) sudo netstat -tlnp | grep 8443
Expected output:
texttcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 1234/nginx tcp 0 0 127.0.0.1:8443 0.0.0.0:* LISTEN 5678/xray
Problem 1: The Nginx configuration test fails
bash# Check the configuration file syntax sudo nginx -t # Look at the detailed error log sudo tail -f /var/log/nginx/error.log
Problem 2: Port 443 is already in use
bash# Show the process occupying port 443 sudo lsof -i :443 # If it is Apache, stop it sudo systemctl stop apache2 sudo systemctl disable apache2
Problem 3: The Nginx stream module is not loaded
bash# Check whether Nginx includes the stream module nginx -V 2>&1 | grep -o with-stream # If the stream module is missing, reinstall an Nginx build that includes it sudo apt install nginx-extras -y
In the "Speed" settings, enable:
json{ "outbounds": [ { "protocol": "vless", "settings": { "vnext": [ { "address": "reality.yourdomain.com", "port": 443, "users": [ { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "encryption": "none", "flow": "xtls-rprx-vision" } ] } ] }, "streamSettings": { "network": "tcp", "security": "reality", "realitySettings": { "serverName": "www.microsoft.com", "fingerprint": "chrome", "publicKey": "xKvN7BqM3pYzWrTgLnFdKs9RmJh6VcXaQwUyE8PbN2o", "shortId": "0123456789abcdef" } } } ] }
Clients that support Reality:
bashsudo systemctl status xray sudo journalctl -u xray -f
bash# Check port listening (443 should be nginx, 8443 should be xray) sudo netstat -tlnp | grep -E "443|8443" # Test the TLS handshake (through the nginx forward) openssl s_client -connect your-ec2-ip:443 # Test the xray port directly (locally) curl -v http://127.0.0.1:8443
Connect with a client application and verify by visiting https://www.google.com.
Add multiple users to the clients array:
json"clients": [ { "id": "user1-uuid-here", "flow": "xtls-rprx-vision" }, { "id": "user2-uuid-here", "flow": "xtls-rprx-vision" } ]
Add routing rules to split traffic:
json"routing": { "rules": [ { "type": "field", "domain": ["geosite:cn"], "outboundTag": "direct" }, { "type": "field", "ip": ["geoip:cn", "geoip:private"], "outboundTag": "direct" } ] }
config.json file regularlynslookup reality.yourdomain.combashsudo systemctl status xray sudo systemctl status nginx
bash# Port 443 should be listened on by nginx # Port 8443 should be listened on by xray sudo netstat -tlnp | grep -E "443|8443"
bashsudo journalctl -u xray -n 50 sudo tail -f /var/log/nginx/error.log
bash# Validate the JSON format cat /usr/local/etc/xray/config.json | jq . # Test the configuration file /usr/local/bin/xray -test -config /usr/local/etc/xray/config.json
If you get a connection timeout error when connecting directly on port 8443 (without going through Nginx):
texterror:8000006E:system library:BIO_connect:Connection timed out
Cause 1: Xray listening address is misconfigured
Check and modify the configuration file, making sure the listen field is set to 0.0.0.0:
bash# Check the current configuration cat /usr/local/etc/xray/config.json | grep -A 3 inbounds # Edit the configuration file sudo nano /usr/local/etc/xray/config.json
Make sure the configuration contains:
json"inbounds": [ { "listen": "0.0.0.0", // listen on all network interfaces "port": 8443, ... } ]
If you use Nginx forwarding, it should be set to 127.0.0.1:
json"inbounds": [ { "listen": "127.0.0.1", // listen locally only "port": 8443, ... } ]
Restart the service after modifying:
bashsudo systemctl restart xray sudo systemctl status xray
Cause 2: Port 8443 is not open in the AWS security group
Cause 3: Blocked by the system firewall
The Ubuntu system may have the ufw firewall enabled:
bash# Check the firewall status sudo ufw status # If it is enabled, add a rule sudo ufw allow 8443/tcp # Or temporarily disable it (not recommended) sudo ufw disable
For systems using iptables:
bash# Check the iptables rules sudo iptables -L -n | grep 8443 # Add an allow rule sudo iptables -A INPUT -p tcp --dport 8443 -j ACCEPT # Save the rules sudo netfilter-persistent save
Cause 4: The Xray service is not running properly
bash# Check the service status sudo systemctl status xray # View detailed logs sudo journalctl -u xray -n 100 --no-pager # Check port listening sudo netstat -tlnp | grep 8443 sudo ss -tlnp | grep 8443
Verify the fix
Test locally on the server:
bash# Local test (should succeed) curl -v http://127.0.0.1:8443 # Test external access (from the server, against the public IP) curl -v http://$(curl -s ifconfig.me):8443
Test from outside:
bash# Test the TCP connection telnet your-ec2-ip 8443 # Or use nc nc -zv your-ec2-ip 8443 # OpenSSL test openssl s_client -connect your-ec2-ip:8443
Q: What is the difference between Reality and TLS?
A: Reality is a new-generation protocol that disguises itself with a real TLS fingerprint, making it harder to detect than traditional TLS.
Q: Why use Cloudflare?
A: Cloudflare provides CDN acceleration, hides your real IP, and offers DDoS protection, among other features.
Q: Can I skip Cloudflare?
A: Yes, you can connect directly using the AWS Elastic IP, but that exposes the server's real IP.
Q: Is shortId required?
A: No. Including the empty string "" in the array allows clients to use no shortId.
Q: How do I change the dest target website?
A: Pick a well-known website that supports TLS 1.3, and update both the dest and serverNames fields.
Disclaimer: Please make sure you comply with local laws and regulations when using this technology. This document is for learning and research purposes only.