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
- 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)
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
- 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
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- making TLS client config: %v
- consolidating TLS connection policies for server %d: %v
- it is unnecessary to specify the TLS listener wrapper in the
- TLS listener wrapper can only be specified once
- server %s: setting up TLS connection policies: %v
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/3a0fde4fb710ec23.
Report an issue: GitHub.