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
- Set a valid, non-empty public_name of at most 255 bytes for ECH.
- Validate generated hostnames (DNS labels <=63 bytes, full name <=255) before feeding them into tls/ech config.
- 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
- Cap generated hostnames at 255 bytes in templating/provisioning code.
- Prefer a short dedicated public_name for ECH.
- Add unit tests validating name bounds before config assembly.
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
- could not convert automation policy subject '%s' to punycode
- marshaling ECH config: %v
- loading ECH DNS provider module: %v
- ECH DNS provider module is not an ECH DNS Provider: %v
- could not determine zone for domain: %w (domain=%s nameserve
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/61cf1588e4d1bbc6.
Report an issue: GitHub.