Borrowing someone else’s TLS: seven checks of the donor site’s REALITY before editing the config

There is a problem with REALITY on the forum already includes a mechanics analysis and a large FAQ about the post-quantum part. But the step that 90% of instructions boil down to “set the dest of any foreign site,” is hardly discussed anywhere. Meanwhile, it is precisely this field that determines how your server looks to the scanner—and how much чужой traffic it will carry for free.

Below are seven candidate checks that can be done from the VPS in a couple of minutes, and a table of realitySettings fields indicating where each value comes from.

The server that goes to someone else’s site for you

Mechanics in one phrase: a connection that has not passed REALITY authentication is your server’s directly forwards to the target. It does not “pretend,” it does not impersonate — it completely forwards, including ClientHello, and returns the real response of the real site to the client.

From here, two consequences that are typically realized after startup. The first is pleasant: anyone who connects to you on port 443 without the correct key will see чужой valid TLS with чужой certificate. The second unpleasant one — directly from the official Project X documentation: if the donor site’s IP is “special,” for example behind a Cloudflare CDN, your server effectively becomes a port-forwarder for Cloudflare. The documentation immediately suggests remedies: filter through Nginx or restrict fallback with the limitFallbackUpload and limitFallbackDownload parameters.

Important:

Choosing a donor is not cosmetic or “masking.” It is the decision about where your VPS will go on every failed handshake, which certificate the scanner will see, and who will pay for the bandwidth.

Official minimal README of the REALITY project is short: foreign site, TLSv1.3 and HTTP/2 support, domain not used for redirects. Nice bonuses — geographically close IP, encrypted handshake messages after Server Hello (the README uses dl.google.com as an example) and OCSP Stapling. All of this is verifiable by hand, which is what we will do.

Check 1. TLSv1.3 — and nothing lower

openssl s_client -connect www.example.tld:443 -servername www.example.tld \
    -alpn h2 -status -tls1_3 </dev/null 2>/dev/null | grep -E "^New,|ALPN|Server Temp Key|OCSP"

The -tls1_3 flag forces the client to offer only 1.3. If the connection cannot be established at all — the candidate is out of the discussion: REALITY borrows handshake characteristics of 1.3, on 1.2 the setup does not work.

What should appear in the output:

The line Server Temp Key: X25519, 253 bits shows the agreed key exchange group. The requirement in REALITY documentation does not insist on it, but it’s useful to look: the closer the donor’s profile is to what a normal large site browser serves, the fewer questions you’ll raise.

Check 2. h2 in ALPN

A separate line of the same output:

ALPN protocol: h2

If it shows http/1.1 or ALPN is not negotiated at all — skip. H2 is listed in README requirements on par with TLSv1.3, and it’s not a formality: the vast majority of live foreign sites that the browser visits serve exactly h2, and a donor without it stands out.

Check 3. The domain should not be a forwarding domain

curl -sI -o /dev/null -w "%{http_code} %{redirect_url}\n" https://example.tld/
curl -sI -o /dev/null -w "%{http_code} %{redirect_url}\n" https://www.example.tld/

A live example on a well-known domain: python.org answers 301 https://www.python.org/, and www.python.org — a clean 200. That is why the requirements say “domain not for redirects,” and in the official examples in serverNames both names are included, but the target uses the one that actually serves content.

Warning:

An apex domain that only redirects to www is a bad donor. Your server will answer the scanner with a 300 on a blank page, and that’s exactly the case where the “site” behaves not like a site.

Check 4. What’s written in SAN — that’s where serverNames come from

openssl s_client -connect www.example.tld:443 -servername www.example.tld </dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

There’s a separate snag here. In serverNames — wildcard is not supported — this is stated in both the README and the documentation of the field. So if the donor’s certificate is issued for *.example.tld, you cannot put *.example.tld in the config: enumerate specific names that actually resolve and serve content.

Error:

"serverNames": ["*.пример.tld"] — is not a “convenient shorthand,” but a non-working config. REALITY does not parse the asterisk.

A useful detail of the same field: the list may contain an empty string "" — this means to accept connections without SNI. On the client, the mirror trick: in serverName you can set an arbitrary IP address, then ClientHello will go out without SNI.

Check 5. Whose address is this, and why CDN is a trap

dig +short www.example.tld

Next, the domain must be checked for ownership — via whois, RIPE, or any ASN lookup service. If there is a large CDN behind the name, recall the quote from the beginning: unauthenticated traffic will go there, and you pay for the bandwidth. The funny thing is that it’s not hard to find such an open forwarder, and it accepts traffic from anyone.

A standard mitigation is a token bucket on fallback connections:

"limitFallbackUpload": {
  "afterBytes": 0,
  "bytesPerSec": 0,
  "burstBytesPerSec": 0
},
"limitFallbackDownload": {
  "afterBytes": 0,
  "bytesPerSec": 0,
  "burstBytesPerSec": 0
}

afterBytes — how many bytes to pass without limits (the first kilobytes are the real answer from the site, which should look normal), bytesPerSec — baseline speed after that threshold, burstBytesPerSec — allowed burst. A value of 0 in bytesPerSec means the limit is disabled.

Security:

Also keep in mind the reverse: any overly “smooth” limits themselves become a sign. The REALITY README directly recommends randomizing such parameters in mass deployments to avoid config duplication byte-for-byte.

Check 6. Latency is measured from your VPS, not your laptop

curl -o /dev/null -s -w "connect=%{time_connect}s tls=%{time_appconnect}s\n" https://www.example.tld/

The “geographically close IP” in the README is about proximity to the server, not to you. Each failed connection is another ping from your VPS to the donor, and its latency adds to yours. Donor in a nearby data center and donor on another continent give noticeably different response profiles.

Check 7. Length of the certificate chain

This check is only needed by those who enable the post-quantum signature mldsa65Seed. The documentation states clearly: after enabling, the certificate returned by target must be longer than 3500 bytes — otherwise the temporary REALITY certificate with the post-quantum signature will be longer than the original, and the substitution will be detectable by size.

openssl s_client -connect www.example.tld:443 -servername www.example.tld -showcerts </dev/null 2>/dev/null \
  | awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' | grep -v CERTIFICATE \
  | tr -d '\n' | base64 -d | wc -c
```The team calculates the total size of the chain in bytes DER. Less than 3500 — either look for another donor, or do not include `mldsa65Seed`.

[info]
The threshold is easily passed by domains with long chains and RSA certificates; short two-link ECDSA chains often do not fit. This is one of those cases where a more modern certificate from the donor works against you.
[/info]

## Stand: all seven checks in one run

```bash
#!/bin/bash
# ./check-target.sh www.пример.tld
h="$1"
o=$(openssl s_client -connect "$h:443" -servername "$h" -alpn h2 -status -tls1_3 </dev/null 2>/dev/null)
[ -z "$o" ] && { echo "$h: TLSv1.3 not negotiated — abort"; exit 1; }
echo "$o" | grep -E "^New,|^ALPN protocol|Server Temp Key|OCSP"
echo "$h" | xargs -I{} curl -sI -o /dev/null -w "HTTP: %{http_code} %{redirect_url}\n" "https://{}/"
echo "$o" | openssl x509 -noout -ext subjectAltName 2>/dev/null | tail -n +2
echo "IP: $(dig +short "$h" | tr '\n' ' ')"
curl -o /dev/null -s -w "connect=%{time_connect}s tls=%{time_appconnect}s\n" "https://$h/"
chain=$(openssl s_client -connect "$h:443" -servername "$h" -showcerts </dev/null 2>/dev/null \
  | awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' | grep -v CERTIFICATE | tr -d '\n' | base64 -d | wc -c)
echo "Chain: $chain bytes (for mldsa65Seed you need > 3500)"

Run it on the VPS itself — otherwise checks 5 and 6 make no sense, and the result of check 1 may differ from what the server will see.

RealitySettings fields and where each value comes from

Field (server) Required What to write Where the value comes from
target yes www.example.tld:443, format as in dest in VLESS fallbacks checks 1–7
xver no PROXY protocol version, format as in xver in fallbacks your front-end scheme
serverNames yes specific names, without *; an empty string is allowed SAN of the certificate, check 4
privateKey yes output of xray x25519 generated locally
shortIds yes up to 16 hex characters, even number; empty value allows client not to send shortId you invent
minClientVer no default 26.3.27 documentation
maxClientVer no upper bound of client version documentation
maxTimeDiff no permissible clock skew, ms your choice
mldsa65Seed no seed of post-quantum signature ML-DSA-65 requires check 7
limitFallbackUpload / limitFallbackDownload no afterBytes, bytesPerSec, burstBytesPerSec check 5

Regarding shortIds, remember the format: 8 bytes = 16 hex characters; the number of characters must be even, and a short value is padded with zeros. The documentation provides an example: `