caddyserver/caddy · error
protocol min (%x) cannot be greater than protocol max (%x)
Error message
protocol min (%x) cannot be greater than protocol max (%x)
What it means
Returned by buildStandardTLSConfig when both protocol bounds are set and the textual ProtocolMin compares greater than ProtocolMax. Versions are compared as strings, which works only because the supported names (tls1.0...tls1.3) sort lexicographically in version order; the intended order in config is min first, max second.
Source
Thrown at modules/caddytls/connpolicy.go:371
// add all the curve preferences in order, without duplicates
curvesAdded := make(map[tls.CurveID]struct{})
for _, curveName := range p.Curves {
curveID := SupportedCurves[curveName]
if _, ok := curvesAdded[curveID]; !ok {
curvesAdded[curveID] = struct{}{}
cfg.CurvePreferences = append(cfg.CurvePreferences, curveID)
}
}
// ensure ALPN includes the ACME TLS-ALPN protocol
alpnFound := slices.Contains(p.ALPN, acmez.ACMETLS1Protocol)
if !alpnFound && (cfg.NextProtos == nil || len(cfg.NextProtos) > 0) {
cfg.NextProtos = append(cfg.NextProtos, acmez.ACMETLS1Protocol)
}
// min and max protocol versions
if (p.ProtocolMin != "" && p.ProtocolMax != "") && p.ProtocolMin > p.ProtocolMax {
return fmt.Errorf("protocol min (%x) cannot be greater than protocol max (%x)", p.ProtocolMin, p.ProtocolMax)
}
if p.ProtocolMin != "" {
cfg.MinVersion = SupportedProtocols[p.ProtocolMin]
}
if p.ProtocolMax != "" {
cfg.MaxVersion = SupportedProtocols[p.ProtocolMax]
}
// client authentication
if p.ClientAuthentication != nil {
if err := p.ClientAuthentication.provision(ctx); err != nil {
return fmt.Errorf("provisioning client CA: %v", err)
}
if err := p.ClientAuthentication.ConfigureTLSConfig(cfg); err != nil {
return fmt.Errorf("configuring TLS client authentication: %v", err)
}
// Prevent privilege escalation in case multiple vhosts are configured forView on GitHub (pinned to 50e54ee279)
Solutions
- Swap the values so the lower version comes first: 'protocol_version tls1.2 tls1.3'
- Or set only one bound if a single limit was intended
- Re-validate with 'caddy validate --config <file>'
Example fix
# before
tls {
protocol_version tls1.3 tls1.2
}
# after
tls {
protocol_version tls1.2 tls1.3
} Defensive patterns
Strategy: validation
Validate before calling
// Validate ordering before building config:
func validProtocolRange(min, max string) bool {
if min == "" || max == "" {
return true
}
return min <= max // works for tls1.0..tls1.3 lexicographic order
} Prevention
- Always write protocol_version as 'oldest newest'
- If generating JSON, assert protocol_min <= protocol_max in the generator
- Run 'caddy validate' on any config touching protocol bounds
When it happens
Trigger: Caddyfile 'protocol_version tls1.3 tls1.2' (arguments reversed); JSON protocol_min="tls1.3", protocol_max="tls1.2"; hand-edited configs swapping the two fields.
Common situations: Users assuming newest-first ordering from other software, or editing the pair manually and inverting them. The %x verbs even render the values oddly (hex-encoded strings), but the names remain readable enough to spot the inversion.
Related errors
- invalid TLS renegotiation level: %v
- unsupported cipher suite: %s
- could not convert automation policy subject '%s' to punycode
- unrecognized key type: %s
- no PEM keys specified
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/5685fa2f589332dd.
Report an issue: GitHub.