{"record":{"id":"a5056732012fc7f4","repo":"caddyserver/caddy","slug":"storing-ech-config-v","errorCode":null,"errorMessage":"storing ECH config: %v","messagePattern":"storing ECH config: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/ech.go","lineNumber":685,"sourceCode":"\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 {\n\t\treturn echConfig{}, fmt.Errorf(\"storing ECH config metadata: %v\", err)\n\t}\n\n\techCfg.privKeyBin = privKeyBytes\n\techCfg.configBin = echConfigBytes // this contains the public key\n\techCfg.meta = meta\n\n\treturn echCfg, nil\n}\n\n// ECH represents an Encrypted ClientHello configuration.\n//\n// EXPERIMENTAL: Subject to change.\ntype ECHConfiguration struct {\n\t// The public server name (SNI) that will be used in the outer ClientHello.\n\t// This should be a domain name for which this server is authoritative,","sourceCodeStart":667,"sourceCodeEnd":703,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/ech.go#L667-L703","documentation":"Second of three storage writes when minting a new ECH config: the serialized ECHConfig (config.bin) is written under ech/configs/<configID>/. Failure means the storage backend rejected the write (permissions, disk full, remote provider error). Note that key.bin was already written successfully, so the config ID directory now holds a partial config; on retry newECHConfigID will treat this ID as taken, which is safe but can strand orphaned key files.","triggerScenarios":"ctx.Storage().Store for ech/configs/<id>/config.bin fails right after key.bin succeeded — typically intermittent storage faults (network blip to remote storage, quota hit mid-provisioning).","commonSituations":"Flaky distributed storage (momentary network partition), disk filling between writes, storage rate-limiting burst writes in the same prefix.","solutions":["Check storage backend health/logs at the timestamp of the failure (the Store error is wrapped in this message).","Retry by restarting/reprovisioning: ECH config creation is retried and a fresh config ID is used.","Optionally clean orphaned partial entries (ech/configs/<id> missing config.bin or meta.json) from storage once storage is healthy.","Add storage-side retries or capacity if this recurs."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"storing ECH config\") && !strings.Contains(err.Error(), \"metadata\") {\n    // key.bin already persisted; after storage repair, restart Caddy — a new ID is allocated\n}","preventionTips":["Use a storage backend with retry/backoff semantics for transient faults.","Periodically prune orphaned ech/configs/<id> dirs missing config.bin/meta.json."],"tags":["tls","ech","storage","partial-write"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}