- lessons-learned: add two critical issues from first real deployment 1. client enable:false causes auto-removal by x-ui scheduler 2. CF proxy strips Connection header, nginx must hardcode WS headers - xray-inbound-config.md: fix API path, add enable:true to client, hardcode Upgrade/Connection headers in nginx WS location
5.3 KiB
Recommended Xray Inbound Configuration
This reference covers the best protocol/transport combinations for security and performance, and the API payloads to create them via the x-ui REST API.
Protocol Recommendation (ranked)
| Rank | Protocol | Transport | TLS | Why |
|---|---|---|---|---|
| ✅ Best | VLESS | WebSocket | TLS (via Nginx) | CDN-friendly, low overhead, widely supported |
| ✅ Best | VLESS | gRPC | TLS (via Nginx) | Multiplexed, low latency, CDN-friendly |
| Good | VLESS | TCP | XTLS/Reality | No CDN needed, excellent performance, anti-detection |
| Good | VMess | WebSocket | TLS (via Nginx) | Broad client support |
| Avoid | VMess | TCP | none | Detectable, no forward secrecy |
| Avoid | Shadowsocks | — | — | Blocked in many regions |
Default recommendation: VLESS + WebSocket + TLS, proxied through Nginx.
- Panel port stays on
127.0.0.1(localhost-only) - Nginx terminates TLS on 443 and forwards to the inbound port on
127.0.0.1 - Inbound port also stays localhost-only (e.g.
127.0.0.1:10000)
Step-by-Step: Create VLESS + WS + TLS Inbound
1. Collect inbound parameters
Ask the user (or use defaults):
| Field | Default |
|---|---|
| Inbound port | 10000 |
| WS path | /ws/ (use a random string for security) |
| Remark | vless-ws |
2. Create inbound via x-ui API
The x-ui panel exposes a REST API at http://127.0.0.1:<panel_port><base_path>.
Authenticate with a session cookie first, then POST the inbound.
# 1. Login and save session cookie
PANEL_USER=$(bash /repo/common-skills/tools/kp-get.sh "<entry_title>" UserName)
PANEL_PASS=$(bash /repo/common-skills/tools/kp-get.sh "<entry_title>" Password)
ssh -i <key_path> -p <ssh_port> <user>@<host> "
curl -sc /tmp/xui-cookie.txt \
-X POST http://127.0.0.1:<panel_port><base_path>login \
-H 'Content-Type: application/json' \
-d '{\"username\":\"'\"$PANEL_USER\"'\",\"password\":\"'\"$PANEL_PASS\"'\"}'
"
# 2. Generate a UUID for the client
UUID=$(ssh -i <key_path> -p <ssh_port> <user>@<host> "cat /proc/sys/kernel/random/uuid")
# 3. Create VLESS + WebSocket inbound
ssh -i <key_path> -p <ssh_port> <user>@<host> "
curl -sb /tmp/xui-cookie.txt \
-X POST http://127.0.0.1:<panel_port><base_path>panel/api/inbounds/add \
-H 'Content-Type: application/json' \
-d '{
\"remark\": \"vless-ws\",
\"enable\": true,
\"protocol\": \"vless\",
\"listen\": \"127.0.0.1\",
\"port\": 10000,
\"settings\": \"{\\\"clients\\\":[{\\\"id\\\":\\\"'\"$UUID\"'\\\",\\\"flow\\\":\\\"\\\"}],\\\"decryption\\\":\\\"none\\\"}\",
\"streamSettings\": \"{\\\"network\\\":\\\"ws\\\",\\\"wsSettings\\\":{\\\"path\\\":\\\"/ws/\\\"}}\",
\"sniffing\": \"{\\\"enabled\\\":true,\\\"destOverride\\\":[\\\"http\\\",\\\"tls\\\"]}\"
}'
"
3. Add Nginx location for the WS path
Append to the existing Nginx config (/etc/nginx/conf.d/x-ui.conf):
location /ws/ {
proxy_pass http://127.0.0.1:10000;
proxy_http_version 1.1;
proxy_set_header Upgrade websocket;
proxy_set_header Connection upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400s;
}
Reload Nginx: systemctl reload nginx
4. Report client config
Output the connection info for the user to import into their client (v2rayN, Clash, etc.):
Protocol : VLESS
Address : <domain>
Port : 443
UUID : <uuid>
Transport: WebSocket
Path : /ws/
TLS : TLS
SNI : <domain>
Or generate a VLESS share link:
vless://<uuid>@<domain>:443?encryption=none&security=tls&type=ws&path=%2Fws%2F&sni=<domain>#vless-ws
Alternative: VLESS + Reality (no CDN, no domain needed)
Reality is the best option when no domain is available. It mimics a real TLS handshake against a target site.
# Generate Reality key pair inside the container/host
ssh -i <key_path> -p <ssh_port> <user>@<host> \
"docker exec x-ui xray x25519" # Docker
# or: xray x25519 # native
Save the Private key and Public key output.
Create inbound via API (replace keys and port):
{
"remark": "vless-reality",
"enable": true,
"protocol": "vless",
"listen": "",
"port": 443,
"settings": "{\"clients\":[{\"id\":\"<uuid>\",\"flow\":\"xtls-rprx-vision\"}],\"decryption\":\"none\"}",
"streamSettings": "{\"network\":\"tcp\",\"security\":\"reality\",\"realitySettings\":{\"show\":false,\"dest\":\"www.microsoft.com:443\",\"serverNames\":[\"www.microsoft.com\"],\"privateKey\":\"<private_key>\",\"shortIds\":[\"\"]}}"
}
Client connection info:
Protocol : VLESS
Address : <vps_ip>
Port : 443
UUID : <uuid>
Flow : xtls-rprx-vision
Transport : TCP
Security : Reality
PublicKey : <public_key>
SNI : www.microsoft.com
Note: Reality listens on
0.0.0.0:443(must be public). This is intentional — it's the proxy traffic port, not the panel.
Security Hardening for Inbounds
- Always set
"listen": "127.0.0.1"for WS/gRPC inbounds (Nginx handles public exposure). - Use a random UUID per client; rotate periodically.
- Use a non-obvious WS path (e.g.
/a3f9k2/not/ws/). - Enable sniffing (
destOverride: ["http","tls"]) to block DNS leaks. - For Reality, use a high-traffic legitimate domain as
dest(e.g.www.microsoft.com,www.apple.com).