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:
| Field | Side | Type | Default | Allowed | Description |
|---|---|---|---|---|---|
echServerKeys | Inbound | string | (unset) | base64 ECH key bundle | Server ECH key set. Generate alongside your TLS cert. |
echConfigList | Outbound | string | (unset) | base64 ECHConfigList | Pin a specific config list. When unset, the client auto-discovers via HTTPS DNS records. |
echForceQuery | Outbound | string | (cascade) | hkdf, dns | Force a particular discovery mechanism. |
echSockopt | Outbound | SocketConfig | (unset) | socket options | Socket 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
serverNamevalue (example.comabove) is the public name — the SNI visible on the wire. TheinnerServerName (the one ECH encrypts) is derived from the request's actual destination. - Pin
echConfigListonly 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
InboundECHOptionsandOutboundECHOptionsstructs undertls.ech. See ECH — sing-box. - mihomo exposes a per-proxy
ech-optsblock with three fields (enable,config,query-server-name). See ECH — mihomo.
Source: infra/conf/transport_internet.go:663-666 · v26.6.1 (94ffd50)
