Contents

NGINX in front of Xray

Why the classic "user → nginx → grpc_pass xray" deployment exists, what NGINX is actually doing there, and when you can delete it.

Half the “how do I set up VLESS” guides end with a diagram that has an NGINX box in the middle: user → nginx → grpc_pass → xray. It looks like ceremony, but that NGINX is doing three specific jobs that the pre-REALITY transports could not do for themselves — and once you know which three, you know exactly when you can delete it. Read this alongside XHTTP and Self-steal; it’s the same question from the fronting side.

NGINX did three jobs the old transports couldn’t

Before REALITY, a VLESS-over-TLS-over-gRPC (or WebSocket) server needed a real certificate, a believable website, and a way to route only the secret path to the proxy — and Xray owned none of those, so NGINX supplied all three.

  • Terminate TLS with a real cert. The transport was plain TLS with a Let’s Encrypt cert for a domain you own; NGINX held the cert and the private key on :443.
  • Serve a real site as camouflage. Anyone (a censor, a curious visitor) who opened https://your-domain/ had to get a genuine web page, or the host was obviously “just a proxy.” NGINX served that page for every path except the secret one.
  • Route the proxy path to the backend. For gRPC, grpc_pass forwarded a specific HTTP/2 service to Xray listening on localhost; for WebSocket/XHTTP, proxy_pass with the Upgrade headers did the same for a secret path. Everything else stayed on the website.
server {
	listen 443 ssl http2;
	server_name your-domain.com;
	ssl_certificate     /etc/letsencrypt/live/your-domain.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;

	location / { root /var/www/site; }              # real page for browsers

	location /your-grpc-service {                    # only the secret path
		grpc_pass grpc://127.0.0.1:2053;             # → Xray gRPC inbound
	}
}
The classic split: NGINX terminates TLS, serves a real site for browsers, and reverse-proxies only the proxy path into Xray.

REALITY collapses all three into Xray

REALITY was designed to make that NGINX unnecessary, because it does the same three jobs itself. It terminates TLS using a borrowed certificate (so there’s no cert to obtain or serve), and Xray’s VLESS inbound has a fallbacks mechanism that routes anything which isn’t a valid proxy request — by SNI, ALPN, and request path — to a dest, exactly the triage location blocks did.

proxy/vless/inbound/inbound.go

The inbound lowercases the real ServerName from the connection, longest-substring-matches it against your fallbacks, then narrows by alpn and path before dialing the fallback — a small web-router built into the proxy. So for a single cover domain, the NGINX box has nothing left to do that Xray isn’t already doing.

Keep NGINX only when the box is genuinely a web host

Delete NGINX for a single-purpose REALITY proxy; keep it when you actually serve web content or need it to route more than the proxy.

Don't

NGINX in front of a single REALITY proxy

Terminating TLS in NGINX and reverse-proxying to a REALITY inbound means two TLS stacks and a redundant cert for one cover domain that REALITY mirrors by itself. It adds a moving part and a second fingerprint for no gain.

Do

NGINX when it earns its keep

Keep it when the host runs multiple virtual hosts, serves a real site you want indexed and logged, shares :443 across several backends, or fronts a plain-TLS XHTTP/gRPC/WS transport where a real cert and a real page are the whole disguise. Then Xray is one location/grpc_pass among many.

The tell: if the thing in front of Xray would serve more than your one cover domain — real pages, real logs, several backends — you want a web server. If it exists only to hold a cert and echo one decoy page, REALITY already is that web server.