caddyserver/caddy · error

making TLS client config: %v

Error message

making TLS client config: %v

What it means

The reverse_proxy HTTP transport delegates to the TLS connection policy module (`h.TLS.MakeTLSClientConfig`) to build a *tls.Config for upstream connections. Any failure there — bad client certificate files, invalid cipher suites, unsupported TLS version bounds — surfaces wrapped with this message during provisioning.

Source

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

	}

	rt := &http.Transport{
		Proxy:                  proxyWrapper,
		DialContext:            dialContext,
		MaxConnsPerHost:        h.MaxConnsPerHost,
		ResponseHeaderTimeout:  time.Duration(h.ResponseHeaderTimeout),
		ExpectContinueTimeout:  time.Duration(h.ExpectContinueTimeout),
		MaxResponseHeaderBytes: h.MaxResponseHeaderSize,
		WriteBufferSize:        h.WriteBufferSize,
		ReadBufferSize:         h.ReadBufferSize,
	}

	if h.TLS != nil {
		rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout)
		var err error
		rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)
		if err != nil {
			return nil, fmt.Errorf("making TLS client config: %v", err)
		}

		serverNameHasPlaceholder := strings.Contains(h.TLS.ServerName, "{")

		// We need to use custom DialTLSContext if:
		// 1. ServerName has a placeholder that needs to be replaced at request-time, OR
		// 2. ProxyProtocol is enabled, because req.URL.Host is modified to include
		//    client address info with "->" separator which breaks Go's address parsing
		if serverNameHasPlaceholder || h.ProxyProtocol != "" {
			rt.DialTLSContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
				// reuses the dialer from above to establish a plaintext connection
				conn, err := dialContext(ctx, network, addr)
				if err != nil {
					return nil, err
				}

				// but add our own handshake logic
				tlsConfig := rt.TLSClientConfig.Clone()

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Read the nested cause after the colon — it names the exact TLS setting that failed
  2. Verify file paths are absolute or relative to where caddy runs, and that cert/key are valid PEM
  3. Restrict tls_versions to tls1.2/tls1.3 and remove cipher overrides unless required (Go 1.3 suites are fixed)
  4. Validate the pair with openssl: openssl x509 -in cert.pem -noout && openssl pkey -in key.pem -noout

Example fix

# before
transport http {
	tls {
		ciphers TLS_ECDHE_RSA_WITH_AES_128_SHA256
	}
}
# after
transport http {
	tls
}
Defensive patterns

Strategy: validation

Validate before calling

// preflight the key material used by the TLS block
func checkTLSFiles(certPath, keyPath, caPath string) error {
	for _, p := range []string{certPath, keyPath, caPath} {
		if p == "" {
			continue
		}
		if _, err := os.Stat(p); err != nil {
			return fmt.Errorf("tls file %s: %w", p, err)
		}
	}
	return nil
}

Prevention

When it happens

Trigger: `transport http { tls_client_auth { cert_file ... key_file ... } }` pointing to unreadable/corrupt PEM files; `tls_versions` with values like `tls1.4`; `ciphers` naming an unsupported suite; bad `tls_trusted_ca_certs` file in reverse_proxy's tls block.

Common situations: Cert paths relative to the wrong working directory, PEM files containing a cert where a key is expected, mTLS rotation leaving a half-updated keypair, or copying TLS settings from an nginx frontend config with different cipher names.

Understand the failure class

Related errors


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