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

  1. Check storage backend health/logs at the timestamp of the failure (the Store error is wrapped in this message).
  2. Retry by restarting/reprovisioning: ECH config creation is retried and a fresh config ID is used.
  3. Optionally clean orphaned partial entries (ech/configs/<id> missing config.bin or meta.json) from storage once storage is healthy.
  4. 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

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


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/a5056732012fc7f4. Report an issue: GitHub.