caddyserver/caddy · error
storing ECH private key: %v
Error message
storing ECH private key: %v
What it means
Caddy persists each new ECH config as three objects under ech/configs/<configID>/ in the configured storage: key.bin (HPKE private key), config.bin, and meta.json. This is the first Store call; it fails when the storage backend rejects the write — permissions, disk full, read-only filesystem, or a remote storage provider error (auth, network, quota). Because it is the first of three writes, a failure here leaves no partial config behind.
Source
Thrown at modules/caddytls/ech.go:682
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 {
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.View on GitHub (pinned to 50e54ee279)
Solutions
- Verify write access of the storage path/backend: have Caddy's user create a file under the storage prefix (default data dir, or your storage module's location).
- Free disk space or fix quota on the storage volume.
- For remote storage modules, check credentials and connectivity (the module usually logs a more specific error just before).
- Restart Caddy after fixing storage; provisioning will retry and find a free config ID.
Example fix
// before: storage dir not writable $ ls -ld /var/lib/caddy drwx------ root root // after $ chown -R caddy:caddy /var/lib/caddy
Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: ensure the storage prefix is writable.
func probeECHStorageWrite(ctx context.Context, stor caddy.Storage) error {
return stor.Store(ctx, "ech/configs/.probe", []byte("ok"))
} Try / catch
if err != nil && strings.Contains(err.Error(), "storing ECH private key") {
// storage-level failure: surface wrapped cause, fix backend, then restart to retry
log.Printf("ECH store failed, check storage: %v", err)
} Prevention
- Run the Caddy service under a user that owns the data directory.
- Monitor disk space and remote storage auth expiry.
- No partial state is left by this specific failure; safe to retry after fixing storage.
When it happens
Trigger: ECH provisioning triggered (first start with ech on, or all existing ECH configs expired/rotated) while ctx.Storage().Store fails: read-only volume, exhausted disk, s3/restic/redis storage auth failure or network outage.
Common situations: Running Caddy in a container with a read-only or unmounted data volume; file_storage directory owned by root while Caddy drops privileges; disk-full nodes; distributed storage briefly unavailable during certificate/ECH provisioning.
Related errors
- creating TLS storage configuration: %v
- generating unique config ID: %v
- storing ECH config: %v
- storing ECH config metadata: %v
- supported version must be %d: got %d
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/578dad014ff7a1f7.
Report an issue: GitHub.