caddyserver/caddy · error
storing ECH config: %v
Error message
storing ECH config: %v
What it means
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.
Source
Thrown at modules/caddytls/ech.go:685
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 {
return echConfig{}, fmt.Errorf("storing ECH config metadata: %v", err)
}
echCfg.privKeyBin = privKeyBytes
echCfg.configBin = echConfigBytes // this contains the public key
echCfg.meta = meta
return echCfg, nil
}
// ECH represents an Encrypted ClientHello configuration.
//
// EXPERIMENTAL: Subject to change.
type ECHConfiguration struct {
// The public server name (SNI) that will be used in the outer ClientHello.
// This should be a domain name for which this server is authoritative,View on GitHub (pinned to 50e54ee279)
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.
Defensive patterns
Strategy: retry
Try / catch
if err != nil && strings.Contains(err.Error(), "storing ECH config") && !strings.Contains(err.Error(), "metadata") {
// key.bin already persisted; after storage repair, restart Caddy — a new ID is allocated
} Prevention
- Use a storage backend with retry/backoff semantics for transient faults.
- Periodically prune orphaned ech/configs/<id> dirs missing config.bin/meta.json.
When it happens
Trigger: 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).
Common situations: Flaky distributed storage (momentary network partition), disk filling between writes, storage rate-limiting burst writes in the same prefix.
Related errors
- storing ECH config metadata: %v
- generating unique config ID: %v
- storing ECH private key: %v
- supported version must be %d: got %d
- depleted attempts to find an available config_id
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/a5056732012fc7f4.
Report an issue: GitHub.