REALITY — sing-box
sing-box implements both REALITY sides. The inbound (reality: sub- block inside the embedded tls: block) impersonates a third-party site during the TLS handshake; the outbound (also nested inside tls:) holds just the public-key half and the short ID.
Inbound reality
Lives under tls.reality. The inbound's tls.enabled and tls.reality.enabled must both be true.
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
enabled | bool | false | true | false | Turn REALITY on for this inbound. When true, the regular TLS handshake is replaced by the REALITY handshake. |
handshake | InboundRealityHandshakeOptions | (required) | InboundRealityHandshakeOptions | Camouflage target. Unauthorized connections (wrong SNI / wrong short_id) are transparently proxied to this address, so observers see traffic flowing to a legitimate site. |
private_key | string | (required) | <base64 X25519 private key> | Server X25519 private key. Generate with `sing-box generate reality-keypair`. |
short_id | badoption.Listable[string] | [] | [<hex string>] | List of allowed short IDs (hex, even length, ≤ 16 chars). One `""` entry allows clients without a short ID. |
max_time_difference | badoption.Duration | 0 (no limit) | <duration> | Maximum tolerated clock skew between client and server. Connections outside this window are rejected. 0 disables the check. |
Source: option/tls.go:193-199 · pinned at v1.13.11 (553cfa1)
handshake
The handshake sub-block embeds ServerOptions (server, server_port) and DialerOptions for the camouflage upstream:
"handshake": {
"server": "www.cloudflare.com",
"server_port": 443,
"bind_interface": "eth0"
}Outbound reality
Lives under tls.reality on the outbound's embedded tls: block.
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
enabled | bool | false | true | false | Turn REALITY on for this outbound. |
public_key | string | (required) | <base64 X25519 public key> | Server X25519 public key. Paired with the server's `private_key`. |
short_id | string | "" | <hex string> | Short ID announced to the server. Must match one of the server's `short_id` entries (or empty if the server allows it). |
Source: option/tls.go:234-238 · pinned at v1.13.11 (553cfa1)
Key generation
$ sing-box generate reality-keypair
PrivateKey: <base64-private-key>
PublicKey: <base64-public-key>The private key goes into the server's private_key; the public key goes into clients' public_key. Short IDs are arbitrary hex strings that you assign per-client.
Examples
Server — VLESS over REALITY impersonating www.cloudflare.com:
{
"inbounds": [{
"type": "vless",
"tag": "vless-reality",
"listen": "::",
"listen_port": 443,
"users": [{ "uuid": "...", "flow": "xtls-rprx-vision" }],
"tls": {
"enabled": true,
"server_name": "www.cloudflare.com",
"reality": {
"enabled": true,
"handshake": {
"server": "www.cloudflare.com",
"server_port": 443
},
"private_key": "<base64-private-key>",
"short_id": ["", "0123456789abcdef"]
}
}
}]
}Client:
{
"outbounds": [{
"type": "vless",
"server": "your.server.example",
"server_port": 443,
"uuid": "...",
"flow": "xtls-rprx-vision",
"tls": {
"enabled": true,
"server_name": "www.cloudflare.com",
"utls": { "enabled": true, "fingerprint": "chrome" },
"reality": {
"enabled": true,
"public_key": "<base64-public-key>",
"short_id": "0123456789abcdef"
}
}
}]
}Notes
handshake.serverandhandshake.server_porttogether form the camouflage target — Xray calls thisdest. Unauthorized connections are transparently proxied to this destination, so the on-wire pattern looks like talking to that site.- The outbound is intentionally minimal — just
public_keyandshort_id. Everything else (SNI, ALPN, fingerprint) comes from the surroundingtlsblock. tls.utlsis almost always set when usingtls.realityon the outbound. Without uTLS, the ClientHello has a Go-stdlib fingerprint that doesn't match a browser and defeats the camouflage.max_time_differenceis the clock-skew rejection window. Configure it generously (e.g. 1 minute) for setups where servers and clients don't share an NTP source.
Cross-core notes
- Xray-core keeps every REALITY field in a single
REALITYConfigstruct (both server and client fields in one place). The camouflage target is thedestfield. See REALITY — Xray-core. - mihomo splits the structs by inbound/outbound and uses kebab-case field names (
public-key,private-key,short-id,server-names). See REALITY — mihomo.
Source: option/tls.go:193-238 · v1.13.11 (553cfa1)
