{"record":{"id":"a6c3406c62d543b5","repo":"caddyserver/caddy","slug":"marshaling-ech-config-v","errorCode":null,"errorMessage":"marshaling ECH config: %v","messagePattern":"marshaling ECH config: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/ech.go","lineNumber":669,"sourceCode":"\t\t\t\tAEADID: hpke.AEAD_AES256GCM,\n\t\t\t},\n\t\t\t{\n\t\t\t\tKDFID:  hpke.KDF_HKDF_SHA256,\n\t\t\t\tAEADID: hpke.AEAD_ChaCha20Poly1305,\n\t\t\t},\n\t\t},\n\t}\n\tmeta := echConfigMeta{\n\t\tCreated: time.Now(),\n\t}\n\n\tprivKeyBytes, err := privateKey.MarshalBinary()\n\tif err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"marshaling ECH private key: %v\", err)\n\t}\n\techConfigBytes, err := echCfg.MarshalBinary()\n\tif err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"marshaling ECH config: %v\", err)\n\t}\n\tmetaBytes, err := json.Marshal(meta)\n\tif err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"marshaling ECH config metadata: %v\", err)\n\t}\n\n\tparentKey := path.Join(echConfigsKey, strconv.Itoa(int(configID)))\n\tkeyKey := path.Join(parentKey, \"key.bin\")\n\tconfigKey := path.Join(parentKey, \"config.bin\")\n\tmetaKey := path.Join(parentKey, \"meta.json\")\n\n\tif err := ctx.Storage().Store(ctx, keyKey, privKeyBytes); err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"storing ECH private key: %v\", err)\n\t}\n\tif err := ctx.Storage().Store(ctx, configKey, echConfigBytes); err != nil {\n\t\treturn echConfig{}, fmt.Errorf(\"storing ECH config: %v\", err)\n\t}\n\tif err := ctx.Storage().Store(ctx, metaKey, metaBytes); err != nil {","sourceCodeStart":651,"sourceCodeEnd":687,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/ech.go#L651-L687","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: site address with a >255-byte generated subdomain\n{\"apps\":{\"tls\":{\"ech\":{\"on\":true}}}}\n\n// after: use a normal-length hostname\nlocalhost:443 {\n    tls {\n        ech {\n            on\n            public_name example.com\n        }\n    }\n}","handlingStrategy":"validation","validationCode":"// Validate before config generation.\nfunc validPublicName(name string) error {\n    if l := len(name); l == 0 || l > 255 {\n        return fmt.Errorf(\"public name length (%d) must be 1-255\", l)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["tls","ech","validation","serialization"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}