Skip to content

ECH — Xray-core

Encrypted Client Hello (ECH) hides the SNI from on-path observers by encrypting the ClientHello with a server-published public key. Xray exposes ECH as four fields on the standard tlsSettings block — no separate ECH struct.

Configuration

ECH lives on streamSettings.tlsSettings:

FieldSideTypeDefaultAllowedDescription
echServerKeysInboundstring(unset)base64 ECH key bundleServer ECH key set. Generate alongside your TLS cert.
echConfigListOutboundstring(unset)base64 ECHConfigListPin a specific config list. When unset, the client auto-discovers via HTTPS DNS records.
echForceQueryOutboundstring(cascade)hkdf, dnsForce a particular discovery mechanism.
echSockoptOutboundSocketConfig(unset)socket optionsSocket options applied to the ECH-discovery DNS request.

Examples

Inbound — serve ECH alongside regular TLS:

json
{
  "streamSettings": {
    "security": "tls",
    "tlsSettings": {
      "serverName": "example.com",
      "certificates": [{ "certificateFile": "/etc/ssl/cert.pem", "keyFile": "/etc/ssl/key.pem" }],
      "echServerKeys": "<base64 ECHConfigList + private keys>"
    }
  }
}

Outbound — auto-discover ECH via HTTPS DNS:

json
{
  "streamSettings": {
    "security": "tls",
    "tlsSettings": {
      "serverName": "example.com",
      "alpn": ["h2", "http/1.1"]
    }
  }
}

ECH is opportunistic — when no explicit echConfigList is set, Xray queries the HTTPS DNS record of serverName and uses the published config if found.

Outbound — pin a specific ECH config:

json
{
  "streamSettings": {
    "security": "tls",
    "tlsSettings": {
      "serverName": "example.com",
      "echConfigList": "<base64 ECHConfigList>",
      "echForceQuery": "dns"
    }
  }
}

Notes

  • ECH requires TLS 1.3. Set minVersion: "1.3" if you want to enforce it at the protocol layer.
  • The ECH serverName value (example.com above) is the public name — the SNI visible on the wire. The inner ServerName (the one ECH encrypts) is derived from the request's actual destination.
  • Pin echConfigList only when you control both ends. Auto-discovery is more robust because the published config can rotate without reconfiguration on the client.
  • echForceQuery: "dns" skips the in-memory HKDF cache and forces a fresh DNS lookup. Useful when keys rotate frequently.

Cross-core notes

  • sing-box has dedicated InboundECHOptions and OutboundECHOptions structs under tls.ech. See ECH — sing-box.
  • mihomo exposes a per-proxy ech-opts block with three fields (enable, config, query-server-name). See ECH — mihomo.

Source: infra/conf/transport_internet.go:663-666 · v26.6.1 (94ffd50)

Core Tutorial by Argsment