← Back to library
Protocols Theory

Vision (xtls-rprx-vision): why you need flow

Reality hides the handshake, but one more signal remains — the pattern of the stream itself. Vision (xtls-rprx-vision) is the flow that breaks it. I explain what this "TLS-in-TLS" is, why it gives you away, and how Vision fixes it. No commands — just the mechanics.

This material is about engineering your own infrastructure and is educational in nature. Complying with the laws of your own jurisdiction is your responsibility.

The problem Reality doesn't close

Reality makes the handshake indistinguishable from an honest visit to someone else's site. But imagine DPI doesn't look at the handshake at all, and instead analyzes an already-established connection — packet sizes, timings, the rhythm of exchange. Here a classic VPN-inside-TLS has a characteristic signature, and it has nothing to do with how elegantly the handshake is assembled.

The signature comes from a simple thing: when you push TLS traffic (an HTTPS site, say) inside a VPN that is itself wrapped in TLS, you get double encryption — TLS inside TLS. Your browser encrypts the request with its TLS, the VPN wraps that in its own TLS. From the outside you see a TLS record, inside which lies another TLS record. And that nesting produces a recognizable length pattern: the sizes of the outer packets correlate with the sizes of the inner ones in a predictable way that doesn't happen with ordinary browsing. A statistical classifier catches this without decrypting anything.

What flow is and where Vision came from

Flow is a VLESS inbound parameter that controls exactly how the data stream is handled at the transport level. The value xtls-rprx-vision enables Vision mode — a mechanism invented precisely to kill the TLS-in-TLS signature.

Vision works at the boundary between the outer TLS (the one masqueraded as a donor via Reality) and the payload. It does two key things.

First — it doesn't re-encrypt what's already encrypted. When Vision sees that TLS traffic is riding inside (and that's most of the modern web — HTTPS everywhere), it recognizes the inner TLS records and passes them through without a second wrapping. The outer encryption layer is stripped for this data, because the data is already encrypted by the browser. No double encryption — no correlating length pattern. That's the meaning behind the abbreviation: "rprx" is about relaying the raw stream without the extra layer.

Second — it mixes in padding at the right places. Even after the double encryption is stripped, small statistical tells remain — the characteristic lengths of the first handshake packets of the inner connection. Vision adds random filler padding, blurring those lengths so the packet-size distribution starts to look like ordinary web traffic rather than a tunnel.

Why Vision and Reality are a pair, not alternatives

They're often confused, as if they were two ways to do the same thing. No. They work at different levels and complement each other:

  • Reality is responsible for security — how the handshake looks. It hides the SNI, reuses someone else's certificate, makes the start of the connection indistinguishable from honest TLS.
  • Vision is responsible for flow — how the data stream behaves after the connection is established. It removes the double encryption and the length pattern.

Reality without Vision closes the handshake but leaves a statistical tell in the stream. Vision without Reality cleans the stream but the handshake exposes the SNI. Together they close both of the main tells DPI uses to catch a VPN — handshake/SNI and traffic behavior. That's exactly why, on TCP transport, the pairing security: reality + flow: xtls-rprx-vision is the mandatory minimum, not an option.

Where Vision does not apply

An important point that's easy to trip over: Vision works only on "raw" TCP transport (raw/tcp). On other transports there is none of it, and there shouldn't be.

  • XHTTP — a transport over HTTP; it has its own stream-packaging mechanics, Vision isn't used there. Trying to set flow is an error — the inbound just won't assemble correctly.
  • gRPC — multiplexing over HTTP/2, also without Vision; the stream is packed into gRPC calls, and the length pattern is blurred by the very nature of the transport.
  • Hysteria2 — QUIC/UDP altogether, a different world, no flow at all.

So Vision is a specific thing for TCP-Reality. The moment you move to XHTTP or gRPC to bypass IP blocking or pass behind a CDN, you change the stream-protection model: there it's the transport itself that protects against the length pattern, not a separate flow. Worth remembering when you assemble a node with several transports — the TCP inbound has flow, the XHTTP/gRPC ones shouldn't.

Practical summary

If you boil it all down to an operator's checklist: on a TCP-Reality inbound, always set flow: xtls-rprx-vision on the client — it's not decoration but the thing that actually stops statistics from working out your tunnel. Without it you've closed the handshake but left the stream behavior open, and under serious DPI that will surface sooner or later. On XHTTP/gRPC/Hysteria, don't touch flow — the protection there is different.

How this looks in a real config and where exactly flow is set — in the practical TCP-Reality articles. You now know the mechanics: Vision isn't about the handshake, it's about making sure the data stream after the handshake doesn't give itself away by packet lengths.

Next guide VLESS + TLS: a basic working config → Article unclear or something off? Message me and I will help or fix it. @notrealvpn →
This material is educational and covers network-infrastructure engineering. You are responsible for complying with the laws of your jurisdiction.