ECH — sing-box
sing-box implements ECH on both sides as a nested sub-block of the tls: block. The structs share the deprecated PQ-signature toggles left over from the experimental TLS 1.3 draft phase — both are no-ops in current builds.
Inbound tls.ech
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
enabled | bool | false | true | false | Turn ECH on for this inbound. |
key | badoption.Listable[string] | [] | <base64 ECH key> | Inline ECH key bundle. Accepts a single string or a list of strings (one PEM-style block per entry). |
key_path | string | (unset) | <file path> | Path to an ECH key bundle file. Mutually exclusive with `key`. |
pq_signature_schemes_enabled | bool | false | true | false | Deprecated. Post-quantum signature schemes are not supported by the Go standard library. |
dynamic_record_sizing_disabled | bool | false | true | false | Deprecated. Setting this had no effect even when it was supported. |
Source: option/tls.go:206-215 · pinned at v1.13.11 (553cfa1)
Outbound tls.ech
| Field | Type | Default | Allowed values | Description |
|---|---|---|---|---|
enabled | bool | false | true | false | Turn ECH on for this outbound. |
config | badoption.Listable[string] | [] | <base64 ECHConfigList> | Pinned ECH config list. When unset, sing-box auto-discovers via HTTPS DNS records. |
config_path | string | (unset) | <file path> | Path-form pinned config. |
query_server_name | string | (server_name) | <hostname> | Hostname used for the HTTPS-record DNS query during ECH auto-discovery. Defaults to the outbound's `tls.server_name`. |
pq_signature_schemes_enabled | bool | false | true | false | Deprecated. |
dynamic_record_sizing_disabled | bool | false | true | false | Deprecated. |
Source: option/tls.go:217-227 · pinned at v1.13.11 (553cfa1)
Examples
Inbound — serve ECH using a key file:
json
{
"inbounds": [{
"type": "vless",
"listen_port": 443,
"users": [{ "uuid": "..." }],
"tls": {
"enabled": true,
"server_name": "example.com",
"certificate_path": "/etc/ssl/cert.pem",
"key_path": "/etc/ssl/key.pem",
"ech": {
"enabled": true,
"key_path": "/etc/sing-box/ech.key"
}
}
}]
}Outbound — opportunistic ECH (auto-discovery):
json
{
"outbounds": [{
"type": "vless",
"server": "example.com",
"server_port": 443,
"uuid": "...",
"tls": {
"enabled": true,
"server_name": "example.com",
"ech": { "enabled": true }
}
}]
}Outbound — pinned ECH config:
json
{
"outbounds": [{
"type": "vless",
"server": "example.com",
"server_port": 443,
"uuid": "...",
"tls": {
"enabled": true,
"server_name": "example.com",
"ech": {
"enabled": true,
"config": ["<base64 ECHConfigList>"],
"query_server_name": "cover.example.com"
}
}
}]
}Notes
- ECH requires TLS 1.3. Set
tls.min_version: "1.3"if you want to enforce it; on1.2the ECH extension is silently dropped. - The inbound's
keyfield accepts a list because operators rotate ECH keys frequently — listing the new key alongside the old one lets clients with cached HKDF state finish their last few requests before switching. - Auto-discovery (outbound
enabled: truewith emptyconfig) queries the HTTPS DNS record forquery_server_name(ortls.server_nameif the override is empty). The resolver chain follows the standard sing-box DNS configuration. - The two deprecated fields (
pq_signature_schemes_enabled,dynamic_record_sizing_disabled) are kept for parser compatibility but have no runtime effect — feel free to remove them from existing configs.
Cross-core notes
- Xray-core exposes ECH as four fields directly on
tlsSettingsrather than a sub-block. See ECH — Xray-core. - mihomo uses a per-proxy
ech-optsblock with the same three user-facing fields (enable,config,query-server-name). See ECH — mihomo.
Source: option/tls.go:206-227 · v1.13.11 (553cfa1)
