caddyserver/caddy · error

supported version must be %d: got %d

Error message

supported version must be %d: got %d

What it means

When decoding a stored ECH config (UnmarshalBinary, code borrowed from goech), the first two bytes must equal draftTLSESNI25 (0xfe0d, the draft-13 ECH version). If the stored config.bin holds a different version — corrupt bytes, a different ECH draft generation, or unrelated data placed at ech/configs/<id>/config.bin — decoding aborts with this message naming expected vs actual version.

Source

Thrown at modules/caddytls/ech.go:976

	if err := echCfg.marshalBinary(&b); err != nil {
		return nil, err
	}
	return b.Bytes()
}

// UnmarshalBinary decodes the data back into an ECH config.
//
// Borrowed from github.com/OmarTariq612/goech with modifications.
// Original code: Copyright (c) 2023 Omar Tariq AbdEl-Raziq
func (echCfg *echConfig) UnmarshalBinary(data []byte) error {
	var content cryptobyte.String
	b := cryptobyte.String(data)

	if !b.ReadUint16(&echCfg.Version) {
		return errInvalidLen
	}
	if echCfg.Version != draftTLSESNI25 {
		return fmt.Errorf("supported version must be %d: got %d", draftTLSESNI25, echCfg.Version)
	}

	if !b.ReadUint16LengthPrefixed(&content) || !b.Empty() {
		return errInvalidLen
	}

	var t cryptobyte.String
	var pk []byte

	if !content.ReadUint8(&echCfg.ConfigID) ||
		!content.ReadUint16((*uint16)(&echCfg.KEMID)) ||
		!content.ReadUint16LengthPrefixed(&t) ||
		!t.ReadBytes(&pk, len(t)) ||
		!content.ReadUint16LengthPrefixed(&t) ||
		len(t)%4 != 0 /* the length of (KDFs and AEADs) must be divisible by 4 */ {
		return errInvalidLen
	}

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Compare the reported 'got' value: 0xfe0d expected; anything else means foreign data.
  2. Delete the offending ech/configs/* entries in storage so Caddy regenerates fresh configs (ECH keys are cheap to rotate).
  3. If it recurs, verify storage integrity and avoid downgrading Caddy below the version that wrote the configs.

Example fix

# before
$ caddy start
 error: supported version must be 65292: got 123

# after: wipe ECH state and let Caddy re-create
$ rm -rf ~/.local/share/caddy/ech/configs/*
$ caddy start
Defensive patterns

Strategy: validation

Validate before calling

// Before passing stored bytes to the loader, check the version field.
func isKnownECHVersion(b []byte) bool {
    return len(b) >= 2 && binary.BigEndian.Uint16(b[:2]) == 0xfe0d
}

Try / catch

if err != nil && strings.Contains(err.Error(), "supported version must be") {
    // stale/foreign stored config: delete ech/configs/* and let Caddy regenerate
}

Prevention

When it happens

Trigger: Loading ECH configs from storage where the binary was written by a Caddy build supporting a different ECH draft version, or the file was corrupted/truncated/replaced; also reading a config list with only the 2-byte version present and mismatched.

Common situations: Upgrading/downgrading Caddy across ECH draft-version changes while reusing the same storage; manual tampering or partial writes in ech/configs/*; storage backend returning garbage due to a bug.

Related errors


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