{"record":{"id":"3a0fde4fb710ec23","repo":"caddyserver/caddy","slug":"making-tls-client-config-for-http-3-transport-v","errorCode":null,"errorMessage":"making TLS client config for HTTP/3 transport: %v","messagePattern":"making TLS client config for HTTP/3 transport: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/caddyhttp/reverseproxy/httptransport.go","lineNumber":502,"sourceCode":"\t\trt.MaxIdleConnsPerHost = h.KeepAlive.MaxIdleConnsPerHost\n\t\trt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout)\n\t}\n\n\tif h.Compression != nil {\n\t\trt.DisableCompression = !*h.Compression\n\t}\n\n\t// configure HTTP/3 transport if enabled; however, this does not\n\t// automatically fall back to lower versions like most web browsers\n\t// do (that'd add latency and complexity, besides, we expect that\n\t// site owners  control the backends), so it must be exclusive\n\tif len(h.Versions) == 1 && h.Versions[0] == \"3\" {\n\t\th.h3Transport = new(http3.Transport)\n\t\tif h.TLS != nil {\n\t\t\tvar err error\n\t\t\th.h3Transport.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"making TLS client config for HTTP/3 transport: %v\", err)\n\t\t\t}\n\n\t\t\tif strings.Contains(h.TLS.ServerName, \"{\") {\n\t\t\t\t// copied from quic-go\n\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)","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/caddyserver/caddy/blob/50e54ee279aa1e504fe218ca49ab6ae16c100410/modules/caddyhttp/reverseproxy/httptransport.go#L484-L520","documentation":"When the transport is configured for HTTP/3 only (`versions 3`), the same TLS connection-policy module is used to build the TLS config for the http3.Transport. Failures in that construction (bad certs, invalid settings) are wrapped with this message during provisioning, failing the config load.","triggerScenarios":"`transport http { versions 3 tls { ... } }` with invalid TLS settings: unreadable client cert/key files, bad CA bundle path, unsupported protocol versions, or root_ca_pems with invalid PEM.","commonSituations":"Enabling HTTP/3 to upstreams (e.g. proxying to another Caddy or a QUIC-capable backend) while copying TLS config that worked for TCP frontends but references wrong paths; also private CA setups where the CA file is misplaced.","solutions":["Inspect the wrapped cause after the colon to identify the failing TLS field","Confirm all cert/CA file paths are readable by the caddy process and contain valid PEM","Test the same TLS block with versions 1.1 2 — if it also fails, the problem is the TLS config, not HTTP/3","Verify the upstream actually speaks HTTP/3 before pinning versions 3 (no fallback occurs by design)"],"exampleFix":"# before\nreverse_proxy h3upstream.internal:443 {\n\ttransport http {\n\t\tversions 3\n\t\ttls_trusted_ca_certs /wrong/path/ca.pem\n\t}\n}\n# after\nreverse_proxy h3upstream.internal:443 {\n\ttransport http {\n\t\tversions 3\n\t\ttls_trusted_ca_certs /etc/caddy/ca.pem\n\t}\n}","handlingStrategy":"validation","validationCode":"if slices.Contains(h.Versions, \"3\") && h.TLS != nil {\n\tif _, err := h.TLS.MakeTLSClientConfig(ctx); err != nil {\n\t\treturn fmt.Errorf(\"h3 TLS preflight: %w\", err)\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify the upstream really speaks HTTP/3 (curl --http3-only) before pinning versions 3","Share and pre-validate one TLS block across h1/h2/h3 configs to isolate failures","Remember versions 3 is exclusive: no automatic downgrade"],"tags":["reverse-proxy","http3","tls","config"],"backgroundTag":null,"analyzedSha":"50e54ee279aa1e504fe218ca49ab6ae16c100410","analyzedAt":"2026-08-15T09:20:21.641Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}