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 match

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Check both wrapped errors: the load error explains why recovery was attempted; the delete error explains why it failed.
  2. Restore read/write access to the storage backend or data directory.
  3. 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.
  4. 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

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


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