caddyserver/caddy · error

public name length (%d) must be in the range 1-255

Error message

public name length (%d) must be in the range 1-255

What it means

On the write path (marshalBinary), before emitting bytes the config is validated: RawPublicName — the DNS name clients see in the ECHConfigList — must be 1-255 bytes, matching the 1-byte length prefix format of ECH configs. Empty names or names over 255 bytes (including any length-prefix overhead considerations) abort serialization. This fires when creating/persisting a new ECH config with a bad public name.

Source

Thrown at modules/caddytls/ech.go:1048

		!content.Empty() {
		return errInvalidLen
	}
	echCfg.RawPublicName = string(rawPublicName)

	return nil
}

var errInvalidLen = errors.New("invalid length")

// marshalBinary writes this config to the cryptobyte builder. If there is an error,
// it will occur before any writes have happened.
func (echCfg echConfig) marshalBinary(b *cryptobyte.Builder) error {
	pk, err := echCfg.PublicKey.MarshalBinary()
	if err != nil {
		return err
	}
	if l := len(echCfg.RawPublicName); l == 0 || l > 255 {
		return fmt.Errorf("public name length (%d) must be in the range 1-255", l)
	}

	b.AddUint16(echCfg.Version)
	b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { // "length" field
		b.AddUint8(echCfg.ConfigID)
		b.AddUint16(uint16(echCfg.KEMID))
		b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
			b.AddBytes(pk)
		})
		b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
			for _, cs := range echCfg.CipherSuites {
				b.AddUint16(uint16(cs.KDFID))
				b.AddUint16(uint16(cs.AEADID))
			}
		})
		b.AddUint8(uint8(min(len(echCfg.RawPublicName)+16, 255)))
		b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
			b.AddBytes([]byte(echCfg.RawPublicName))

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Set a valid, non-empty public_name of at most 255 bytes for ECH.
  2. Validate generated hostnames (DNS labels <=63 bytes, full name <=255) before feeding them into tls/ech config.
  3. Use a short shared outer name (e.g. ech.example.com) as the public name.

Example fix

// before
public_name := fmt.Sprintf("%s.%s.%s.example.com", tenant, user, session) // may exceed 255

// after
publicName := "tls-ech.example.com" // fixed short outer name
if len(publicName) == 0 || len(publicName) > 255 {
    return fmt.Errorf("public name length invalid")
}
Defensive patterns

Strategy: validation

Validate before calling

func validatePublicName(name string) error {
    if l := len(name); l == 0 || l > 255 {
        return fmt.Errorf("public name length (%d) must be in the range 1-255", l)
    }
    for _, label := range strings.Split(name, ".") {
        if len(label) > 63 {
            return fmt.Errorf("label too long: %s", label)
        }
    }
    return nil
}

Prevention

When it happens

Trigger: Creating an ECH config whose public_name is empty or a hostname longer than 255 bytes — misconfigured site label, programmatic address generation without length checks.

Common situations: Auto-generated ultra-long subdomains; configs where the public name field is omitted and defaults to empty; copy-paste of a full URL into a name field.

Related errors


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