Deploy a VLESS+Reality proxy server that's invisible to censorship systems. Fully automated. Takes 2 minutes.
Setup
You need a VPS (Debian/Ubuntu, root SSH key access) and a terminal. The script installs everything else automatically.
curl -sS https://raw.githubusercontent.com/uburuntu/meridian/main/setup.sh | bash
It asks for your server IP, configures the proxy, and outputs a QR code. Or pass the IP directly:
curl -sS https://raw.githubusercontent.com/uburuntu/meridian/main/setup.sh | bash -s -- YOUR_IP
curl -sS https://raw.githubusercontent.com/uburuntu/meridian/main/setup.sh | bash -s -- YOUR_IP --domain example.com
What happens
Connect
The script generates an HTML file with a QR code and an "Open in App" deep link. Send it to whoever needs it — they tap once and connect.
Architecture
Single server. VLESS+Reality on port 443 — traffic looks like a normal HTTPS connection to microsoft.com. Censors and probes see a legitimate TLS certificate.
With a domain: HAProxy routes by TLS SNI on port 443 without terminating encryption. Reality traffic goes to Xray, domain traffic goes to Caddy (decoy website + CDN fallback via Cloudflare).
Two servers for when direct foreign connections are blocked. A domestic relay on a whitelisted IP accepts VLESS+TCP and forwards everything abroad via VLESS+Reality+XHTTP.
Local sites are routed directly from the relay. The exit node also keeps a direct Reality+TCP fallback on port 8444.
Reference
| Variable | Default | Description |
|---|---|---|
| reality_sni | www.microsoft.com | Site that Reality impersonates |
| utls_fingerprint | chrome | TLS client fingerprint |
| client_limit_ip | 2 | Max simultaneous IPs per client |
| threexui_version | 2.6.0 | Pinned 3x-ui Docker image version |
| ssh_disable_password | true | Disable SSH password auth |
| bbr_enabled | true | TCP BBR congestion control |
For optimal stealth, use RealiTLScanner to find an SNI target near your server's datacenter.
Follow this order to avoid TLS certificate issues:
1. Add domain in Cloudflare, create A record to server IP
2. Keep cloud icon grey ("DNS only") — don't proxy yet
3. Run the playbook — Caddy gets the TLS cert automatically
4. Switch to orange cloud (Proxied)
5. SSL/TLS → Full (Strict), Network → Enable WebSockets
Caddy obtains certs via HTTP-01 on port 80. If Cloudflare's "Always Use HTTPS" is active, it breaks the challenge. Disable it or add a page rule for /.well-known/acme-challenge/*.
Update Xray and 3x-ui:
Or re-run the setup command — it's safe to repeat. Add more users via the 3x-ui panel (access through SSH tunnel).
For full control over inventory and variables:
Connection fails immediately: Check device clock is accurate within 30 seconds. Enable "Set Automatically" in date/time settings.
Connection stops after days: Server IP may have been blocked. Switch to CDN fallback, or get a new IP and re-run.
Slow speeds: Some hosting IP ranges are throttled. Try a different provider or datacenter (Finland, Netherlands, Sweden).
Do NOT run other protocols (OpenVPN, WireGuard) on the same server — it flags the IP.
Credentials are saved locally in credentials/ (git-ignored, mode 0600). The panel binds to localhost — access via SSH tunnel only. SSH password auth is disabled. UFW allows only ports 22 and 443. Automatic security updates are enabled.
The 3x-ui Docker image is pinned to a tested version. All secrets use no_log: true in Ansible output.