Skip to content

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.

FieldTypeDefaultAllowed valuesDescription
enabledboolfalsetrue | falseTurn REALITY on for this inbound. When true, the regular TLS handshake is replaced by the REALITY handshake.
handshakeInboundRealityHandshakeOptions(required)InboundRealityHandshakeOptionsCamouflage target. Unauthorized connections (wrong SNI / wrong short_id) are transparently proxied to this address, so observers see traffic flowing to a legitimate site.
private_keystring(required)<base64 X25519 private key>Server X25519 private key. Generate with `sing-box generate reality-keypair`.
short_idbadoption.Listable[string][][<hex string>]List of allowed short IDs (hex, even length, ≤ 16 chars). One `""` entry allows clients without a short ID.
max_time_differencebadoption.Duration0 (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:

json
"handshake": {
  "server": "www.cloudflare.com",
  "server_port": 443,
  "bind_interface": "eth0"
}

Outbound reality

Lives under tls.reality on the outbound's embedded tls: block.

FieldTypeDefaultAllowed valuesDescription
enabledboolfalsetrue | falseTurn REALITY on for this outbound.
public_keystring(required)<base64 X25519 public key>Server X25519 public key. Paired with the server's `private_key`.
short_idstring""<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

sh
$ 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:

json
{
  "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:

json
{
  "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.server and handshake.server_port together form the camouflage target — Xray calls this dest. 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_key and short_id. Everything else (SNI, ALPN, fingerprint) comes from the surrounding tls block.
  • tls.utls is almost always set when using tls.reality on the outbound. Without uTLS, the ClientHello has a Go-stdlib fingerprint that doesn't match a browser and defeats the camouflage.
  • max_time_difference is 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 REALITYConfig struct (both server and client fields in one place). The camouflage target is the dest field. 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)

Core Tutorial by Argsment