caddyserver/caddy · error
marshaling ECH config: %v
Error message
marshaling ECH config: %v
What it means
After building an echConfig struct (version draft-ffmpeg 0xfe0d, config ID, KEM, cipher suites, public name), Caddy serializes it with MarshalBinary before storing it at ech/configs/<id>/config.bin. Marshaling fails when the public name (RawPublicName, i.e. the client-visible DNS name in the ECHConfigList) is empty or longer than 255 bytes, or when the HPKE public key cannot be serialized. The public-name bound is the realistic trigger because it derives from the site address.
Source
Thrown at modules/caddytls/ech.go:669
AEADID: hpke.AEAD_AES256GCM,
},
{
KDFID: hpke.KDF_HKDF_SHA256,
AEADID: hpke.AEAD_ChaCha20Poly1305,
},
},
}
meta := echConfigMeta{
Created: time.Now(),
}
privKeyBytes, err := privateKey.MarshalBinary()
if err != nil {
return echConfig{}, fmt.Errorf("marshaling ECH private key: %v", err)
}
echConfigBytes, err := echCfg.MarshalBinary()
if err != nil {
return echConfig{}, fmt.Errorf("marshaling ECH config: %v", err)
}
metaBytes, err := json.Marshal(meta)
if err != nil {
return echConfig{}, fmt.Errorf("marshaling ECH config metadata: %v", err)
}
parentKey := path.Join(echConfigsKey, strconv.Itoa(int(configID)))
keyKey := path.Join(parentKey, "key.bin")
configKey := path.Join(parentKey, "config.bin")
metaKey := path.Join(parentKey, "meta.json")
if err := ctx.Storage().Store(ctx, keyKey, privKeyBytes); err != nil {
return echConfig{}, fmt.Errorf("storing ECH private key: %v", err)
}
if err := ctx.Storage().Store(ctx, configKey, echConfigBytes); err != nil {
return echConfig{}, fmt.Errorf("storing ECH config: %v", err)
}
if err := ctx.Storage().Store(ctx, metaKey, metaBytes); err != nil {View on GitHub (pinned to 50e54ee279)
Solutions
- Verify the site address/public name for ECH is a valid DNS hostname of length 1-255 bytes.
- Shorten or fix the hostname in the tls/ech configuration.
- Check for a mismatch between the configured name and what the ECH provisioning code derives (e.g. empty string) and set it explicitly.
Example fix
// before: site address with a >255-byte generated subdomain
{"apps":{"tls":{"ech":{"on":true}}}}
// after: use a normal-length hostname
localhost:443 {
tls {
ech {
on
public_name example.com
}
}
} Defensive patterns
Strategy: validation
Validate before calling
// Validate before config generation.
func validPublicName(name string) error {
if l := len(name); l == 0 || l > 255 {
return fmt.Errorf("public name length (%d) must be 1-255", l)
}
return nil
} Prevention
- Validate hostname lengths (<=255 bytes) in any pipeline that generates site addresses.
- Set an explicit, short public_name for ECH instead of deriving it from long generated names.
When it happens
Trigger: Enabling ECH for a site whose public_name (the outer/公开 name used in the ECH config) is empty or exceeds 255 bytes, e.g. an extremely long hostname or a mis-parsed address; or a corrupted/generated HPKE public key.
Common situations: Very long subdomain chains in automated/large deployments; hand-edited JSON configs that supply an invalid ech public name; zero-length name when the SNI of a catch-all site is dropped into ECH config creation.
Related errors
- supported version must be %d: got %d
- invalid KEM ID: %d
- parsing public_key: %w
- invalid KDF ID: %d
- invalid AEAD ID: %d
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/a6c3406c62d543b5.
Report an issue: GitHub.