VLESS
A stateless, encryption-free proxy protocol — TLS does the crypto, VLESS just routes.
VLESS answers one question per connection — which user wants to reach which address — and then gets out of the way. There is no cipher negotiation, no timestamp dance, no per-packet MAC: the protocol assumes it lives inside TLS or REALITY, so encrypting again would only burn CPU. What’s left is a header small enough to read in one sitting.
The whole protocol fits in one header
Everything VLESS ever says is said in the first few dozen bytes — after EncodeRequestHeader returns, a TCP connection is a dumb pipe (only UDP and Vision add framing,
covered below). For a TCP request to example.com:443 the client sends exactly this:
00 version, always 0
9a d3 … (16 bytes) user UUID, raw — no hash, no timestamp
00 addons length (0 unless flow is xtls-rprx-vision)
01 command: 01 TCP · 02 UDP · 03 Mux · 04 reverse
01 bb port 443 — the port comes BEFORE the address
02 0b 65 78 61 6d 70 6c 65 2e 63 6f 6d type 02 = domain, "example.com" The address parser is built with protocol.PortThenAddress(), so unlike SOCKS the two bytes of
port precede the address. Address types are 01 IPv4, 02 domain (length-prefixed), 03 IPv6. For Mux and reverse commands the address is omitted entirely. The server’s reply header
is even smaller: it echoes the version byte plus one addons-length byte — two bytes, then
raw payload forever.
Authentication is a map lookup, not cryptography
The UUID is compared as sixteen raw bytes — DecodeRequestHeader reads them off the wire
and calls validator.Get(id); if the map returns a user, you’re in. There is no proof of
possession, no nonce, no replay window like VMess maintains — anyone who has ever seen those
bytes can authenticate forever:
if request.User = validator.Get(id); request.User == nil {
u := uuid.UUID(id)
return nil, nil, nil, isfb, errors.New("invalid request user id: " + u.String())
} This is the trade that makes VLESS lighter than VMess: no AEAD per chunk, no time-skewed auth IDs to compute and verify — just a lookup. It is only safe because the outer layer already provides confidentiality, integrity, and replay protection. Strip that layer and every UUID and destination crosses the wire in cleartext.
Run VLESS over bare TCP
Nothing in the code stops you — and nothing protects you. The UUID (your only credential) and every destination you visit are readable by any on-path observer, and can be replayed to your server verbatim.
Pair it with TLS 1.3 or REALITY
security: "reality" or "tls" on the stream settings gives the protocol the crypto it
deliberately doesn’t carry. With flow: "xtls-rprx-vision" the inbound actively rejects outer
TLS below 1.3.
flow is one word that swaps the entire data plane
The addons block exists for a single value: xtls-rprx-vision. EncodeHeaderAddons writes a length-prefixed protobuf only when the flow is vless.XRV; for every other account it
writes a lone 0x00. When Vision is on, the body writer is replaced by proxy.NewVisionWriter and the inbound uses unsafe.Pointer reflection to grab the private input/rawInput buffers
of the Go TLS connection — the hooks that later let inner TLS records be spliced to the socket
without re-encryption. That mechanism has its own page; what matters here
is that VLESS the protocol only carries the flag, one protobuf field in the header.
Vision also polices what it can’t protect: the outbound rejects UDP/443 (QUIC would be
TLS-in-TLS with visible characteristics) unless you opt in with xtls-rprx-vision-udp443, and
a server whose account declares Vision refuses plain-flow TCP clients outright.
A wrong UUID never gets an error — it gets a web server
With fallbacks configured, failed decoding doesn’t close the connection; it replays it. The inbound buffers the first read, and if the header doesn’t validate (or is shorter than 18
bytes), it picks a fallback by SNI, ALPN, and sniffed HTTP path, dials it, and forwards the
buffered bytes untouched —
optionally with a PROXY-protocol prefix. An active prober speaking anything but perfect VLESS
sees whatever nginx you parked on the fallback port.
{
"protocol": "vless",
"settings": {
"clients": [{ "id": "9ad3f9f7-33aa-45c1-b431-3c73dba872bd", "flow": "xtls-rprx-vision" }],
"decryption": "none",
"fallbacks": [{ "dest": 8080 }]
},
"streamSettings": { "security": "reality" }
} One app connection is one tunnel connection
VLESS has no streams of its own — every connection your browser opens becomes its own
transport connection carrying its own VLESS header, its own TLS handshake underneath.
Multiplexing exists, but as a layer on top: command 0x03 marks the connection as carrying Mux.Cool frames, and UDP quietly rides that path too — the outbound rewrites
UDP requests to command Mux, destination v1.mux.cool:666 (XUDP): always under Vision flow, and
in cone mode for every port except 53 and 443. Plain UDP bodies instead become
2-byte-length-prefixed packets via MultiLengthPacketWriter. If per-connection
handshake cost hurts you, that’s the page to read next.