caddyserver/caddy · error
protocol argument was not a string
Error message
protocol argument was not a string
What it means
loadECHConfig could not load an ECH private key (key.bin under ech/configs/<id>) from storage, AND the compensating cleanup (deleting the config folder to free the config ID) also failed. Caddy tolerates a missing/corrupt key by deleting the folder, so this error means storage is doubly broken: load fails and delete fails.
Source
Thrown at modules/caddyhttp/matchers.go:1423
}
return nil
}
// CELLibrary produces options that expose this matcher for use in CEL
// expression matchers.
//
// Example:
//
// expression protocol('https')
func (MatchProtocol) CELLibrary(_ caddy.Context) (cel.Library, error) {
return CELMatcherImpl(
"protocol",
"protocol_request_string",
[]*cel.Type{cel.StringType},
func(data ref.Val) (RequestMatcherWithError, error) {
protocolStr, ok := data.(types.String)
if !ok {
return nil, errors.New("protocol argument was not a string")
}
return MatchProtocol(strings.ToLower(string(protocolStr))), nil
},
)
}
// CaddyModule returns the Caddy module information.
func (MatchTLS) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.matchers.tls",
New: func() caddy.Module { return new(MatchTLS) },
}
}
// Match returns true if r matches m.
func (m MatchTLS) Match(r *http.Request) bool {
match, _ := m.MatchWithError(r)
return matchView on GitHub (pinned to 50e54ee279)
Solutions
- Check both wrapped errors: the load error explains why recovery was attempted; the delete error explains why it failed.
- Restore read/write access to the storage backend or data directory.
- Manually remove the ech/configs/<id> folder named in the error once storage is healthy, then restart so the config ID is freed and regenerated.
- Prevent manual deletion of individual files (key.bin) inside config folders — delete whole folders only.
Example fix
# before: partially-deleted config leaves stray folder rm /var/lib/caddy/ech/configs/42/key.bin # breaks load # after: remove the whole config folder rm -rf /var/lib/caddy/ech/configs/42
Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight: confirm the storage tree is readable AND deletable before ECH use:
if _, err := storage.Load(ctx, path.Join("ech/configs", id, "key.bin")); err != nil {
if err := storage.Delete(ctx, path.Join("ech/configs", id)); err != nil {
return fmt.Errorf("storage cannot recover from corrupt config: %w", err)
}
} Try / catch
if err != nil { if delErr != nil { // storage doubly broken: halt, require operator intervention on the named cfgIDKey } } — the error message includes the exact storage key to clean up. Prevention
- Never make the data directory read-only while ECH is enabled.
- Delete whole config folders, never individual key.bin files.
- Verify delete permissions when hardening/SELinux policies are applied.
When it happens
Trigger: storage.Load of key.bin errors AND storage.Delete of the cfgIDKey folder errors: read-only storage (load permission denied, delete not permitted), backend outage affecting both operations, or the folder key exists but the child key is missing on a backend where Load errors on missing keys.
Common situations: Data directory made read-only (disk error, Docker read-only mount, SELinux); partial storage states where key.bin was deleted but the folder remains; remote storage outages.
Related errors
- %s is invalid policy
- ErrInvalidSplitPath
- rfc9112 forbids empty Host
- private key does not match issuer public key
- --input is required
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/3ca30367b0398ef4.
Report an issue: GitHub.