{"record":{"id":"c3c3da24043e8923","repo":"caddyserver/caddy","slug":"if-http-3-is-enabled-to-the-upstream-no-other-htt","errorCode":null,"errorMessage":"if HTTP/3 is enabled to the upstream, no other HTTP versions are supported","messagePattern":"if HTTP/3 is enabled to the upstream, no other HTTP versions are supported","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/reverseproxy/httptransport.go","lineNumber":525,"sourceCode":"\t\t\t\tudpConn, err := net.ListenUDP(\"udp\", nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, fmt.Errorf(\"making udp socket for HTTP/3 transport: %v\", err)\n\t\t\t\t}\n\t\t\t\th.quicTransport = &quic.Transport{Conn: udpConn}\n\t\t\t\th.h3Transport.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {\n\t\t\t\t\t// tlsCfg is already cloned from h3Transport.TLSClientConfig\n\t\t\t\t\trepl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)\n\t\t\t\t\ttlsCfg.ServerName = repl.ReplaceAll(tlsCfg.ServerName, \"\")\n\t\t\t\t\tudpAddr, err := resolveUDPAddr(ctx, \"udp\", addr)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn nil, err\n\t\t\t\t\t}\n\t\t\t\t\treturn h.quicTransport.DialEarly(ctx, udpAddr, tlsCfg, cfg)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else if len(h.Versions) > 1 && slices.Contains(h.Versions, \"3\") {\n\t\treturn nil, fmt.Errorf(\"if HTTP/3 is enabled to the upstream, no other HTTP versions are supported\")\n\t}\n\n\t// if h2/c is enabled, configure it explicitly\n\tif slices.Contains(h.Versions, \"2\") || slices.Contains(h.Versions, \"h2c\") {\n\t\tif err := http2.ConfigureTransport(rt); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// DisableCompression from h2 is configured by http2.ConfigureTransport\n\t\t// Likewise, DisableKeepAlives from h1 is used too.\n\n\t\t// Protocols field is only used when the request is not using TLS,\n\t\t// http1/2 over tls is still allowed\n\t\tif slices.Contains(h.Versions, \"h2c\") {\n\t\t\trt.Protocols = new(http.Protocols)\n\t\t\trt.Protocols.SetUnencryptedHTTP2(true)\n\t\t\trt.Protocols.SetHTTP1(false)\n\t\t}","sourceCodeStart":507,"sourceCodeEnd":543,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/reverseproxy/httptransport.go#L507-L543","documentation":"Caddy's HTTP/3 upstream support is exclusive: a reverse_proxy transport http may select version 3 only by itself. When Versions contains \"3\" alongside any other entry (e.g. \"1\", \"2\", \"2c/h2c\"), MakeTLSClientConfig/newTransport returns this error at provision time because no meaningful fallback negotiation exists for QUIC-only dials in this design.","triggerScenarios":"Configuring transport http { versions 3 1 }, versions 3 h2c, or the JSON equivalent \"versions\": [\"3\",\"2\"]. The guard triggers when len(h.Versions) > 1 && slices.Contains(h.Versions, \"3\") during Handler provisioning, so any multi-version list including \"3\" fails immediately when the config is loaded.","commonSituations":"Copy-pasting a versions list built for TCP transports (1 2 h2c) and appending 3 'for future-proofing'; migrating a config from a fork or older doc that appeared to allow mixed versions; JSON configs generated by tooling that always emits [\"1\",\"2\",\"3\"].","solutions":["Set versions to exactly the single value 3: transport http { versions 3 }.","If you need HTTP/1.1 or H2 upstreams, remove 3 from the list (versions 1 2 is fine; the error only fires when 3 coexists with others).","If you need both H3 and H1/H2 upstreams, define two reverse_proxy handler blocks with different matchers, each with its own transport and version set."],"exampleFix":"// before (Caddyfile)\nreverse_proxy localhost:443 {\n    transport http {\n        versions 3 2 1\n        tls\n    }\n}\n\n// after\nreverse_proxy localhost:443 {\n    transport http {\n        versions 3\n        tls\n    }\n}","handlingStrategy":"validation","validationCode":"// Validate before deploy\nfunc validateVersions(versions []string) error {\n    if len(versions) > 1 && slices.Contains(versions, \"3\") {\n        return fmt.Errorf(\"HTTP/3 must be the only version, got %v\", versions)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat versions 3 as exclusive in config templates and linters.","Run caddy validate --config after any change to the versions list.","Split H3 and H1/H2 upstreams into separate reverse_proxy blocks from the start."],"tags":["config","http3","versions","reverseproxy","validation"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}