caddyserver/caddy · error

making TLS client config for HTTP/3 transport: %v

Error message

making TLS client config for HTTP/3 transport: %v

What it means

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.

Source

Thrown at modules/caddyhttp/reverseproxy/httptransport.go:502

		rt.MaxIdleConnsPerHost = h.KeepAlive.MaxIdleConnsPerHost
		rt.IdleConnTimeout = time.Duration(h.KeepAlive.IdleConnTimeout)
	}

	if h.Compression != nil {
		rt.DisableCompression = !*h.Compression
	}

	// configure HTTP/3 transport if enabled; however, this does not
	// automatically fall back to lower versions like most web browsers
	// do (that'd add latency and complexity, besides, we expect that
	// site owners  control the backends), so it must be exclusive
	if len(h.Versions) == 1 && h.Versions[0] == "3" {
		h.h3Transport = new(http3.Transport)
		if h.TLS != nil {
			var err error
			h.h3Transport.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)
			if err != nil {
				return nil, fmt.Errorf("making TLS client config for HTTP/3 transport: %v", err)
			}

			if strings.Contains(h.TLS.ServerName, "{") {
				// copied from quic-go
				udpConn, err := net.ListenUDP("udp", nil)
				if err != nil {
					return nil, fmt.Errorf("making udp socket for HTTP/3 transport: %v", err)
				}
				h.quicTransport = &quic.Transport{Conn: udpConn}
				h.h3Transport.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (*quic.Conn, error) {
					// tlsCfg is already cloned from h3Transport.TLSClientConfig
					repl := ctx.Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
					tlsCfg.ServerName = repl.ReplaceAll(tlsCfg.ServerName, "")
					udpAddr, err := resolveUDPAddr(ctx, "udp", addr)
					if err != nil {
						return nil, err
					}
					return h.quicTransport.DialEarly(ctx, udpAddr, tlsCfg, cfg)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Inspect the wrapped cause after the colon to identify the failing TLS field
  2. Confirm all cert/CA file paths are readable by the caddy process and contain valid PEM
  3. Test the same TLS block with versions 1.1 2 — if it also fails, the problem is the TLS config, not HTTP/3
  4. Verify the upstream actually speaks HTTP/3 before pinning versions 3 (no fallback occurs by design)

Example fix

# before
reverse_proxy h3upstream.internal:443 {
	transport http {
		versions 3
		tls_trusted_ca_certs /wrong/path/ca.pem
	}
}
# after
reverse_proxy h3upstream.internal:443 {
	transport http {
		versions 3
		tls_trusted_ca_certs /etc/caddy/ca.pem
	}
}
Defensive patterns

Strategy: validation

Validate before calling

if slices.Contains(h.Versions, "3") && h.TLS != nil {
	if _, err := h.TLS.MakeTLSClientConfig(ctx); err != nil {
		return fmt.Errorf("h3 TLS preflight: %w", err)
	}
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/3a0fde4fb710ec23. Report an issue: GitHub.