{"record":{"id":"5685fa2f589332dd","repo":"caddyserver/caddy","slug":"protocol-min-x-cannot-be-greater-than-protocol","errorCode":null,"errorMessage":"protocol min (%x) cannot be greater than protocol max (%x)","messagePattern":"protocol min \\(%x\\) cannot be greater than protocol max \\(%x\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddytls/connpolicy.go","lineNumber":371,"sourceCode":"\t// add all the curve preferences in order, without duplicates\n\tcurvesAdded := make(map[tls.CurveID]struct{})\n\tfor _, curveName := range p.Curves {\n\t\tcurveID := SupportedCurves[curveName]\n\t\tif _, ok := curvesAdded[curveID]; !ok {\n\t\t\tcurvesAdded[curveID] = struct{}{}\n\t\t\tcfg.CurvePreferences = append(cfg.CurvePreferences, curveID)\n\t\t}\n\t}\n\n\t// ensure ALPN includes the ACME TLS-ALPN protocol\n\talpnFound := slices.Contains(p.ALPN, acmez.ACMETLS1Protocol)\n\tif !alpnFound && (cfg.NextProtos == nil || len(cfg.NextProtos) > 0) {\n\t\tcfg.NextProtos = append(cfg.NextProtos, acmez.ACMETLS1Protocol)\n\t}\n\n\t// min and max protocol versions\n\tif (p.ProtocolMin != \"\" && p.ProtocolMax != \"\") && p.ProtocolMin > p.ProtocolMax {\n\t\treturn fmt.Errorf(\"protocol min (%x) cannot be greater than protocol max (%x)\", p.ProtocolMin, p.ProtocolMax)\n\t}\n\tif p.ProtocolMin != \"\" {\n\t\tcfg.MinVersion = SupportedProtocols[p.ProtocolMin]\n\t}\n\tif p.ProtocolMax != \"\" {\n\t\tcfg.MaxVersion = SupportedProtocols[p.ProtocolMax]\n\t}\n\n\t// client authentication\n\tif p.ClientAuthentication != nil {\n\t\tif err := p.ClientAuthentication.provision(ctx); err != nil {\n\t\t\treturn fmt.Errorf(\"provisioning client CA: %v\", err)\n\t\t}\n\t\tif err := p.ClientAuthentication.ConfigureTLSConfig(cfg); err != nil {\n\t\t\treturn fmt.Errorf(\"configuring TLS client authentication: %v\", err)\n\t\t}\n\n\t\t// Prevent privilege escalation in case multiple vhosts are configured for","sourceCodeStart":353,"sourceCodeEnd":389,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddytls/connpolicy.go#L353-L389","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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>'"],"exampleFix":"# before\ntls {\n\tprotocol_version tls1.3 tls1.2\n}\n\n# after\ntls {\n\tprotocol_version tls1.2 tls1.3\n}","handlingStrategy":"validation","validationCode":"// Validate ordering before building config:\nfunc validProtocolRange(min, max string) bool {\n\tif min == \"\" || max == \"\" {\n\t\treturn true\n\t}\n\treturn min <= max // works for tls1.0..tls1.3 lexicographic order\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["caddy","tls","protocol-versions","configuration","validation"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}