caddyserver/caddy · error

client_certificate_key_file specified without client_certifi

Error message

client_certificate_key_file specified without client_certificate_file

What it means

Mirror of the pairing check for mTLS: a private key file was supplied via client_certificate_key_file but client_certificate_file is empty. Since a key alone cannot authenticate the client, MakeTLSClientConfig rejects the combination during provisioning before any connection is attempted.

Source

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

	// The value can be a list of any valid tcp port numbers, default empty.
	ExceptPorts []string `json:"except_ports,omitempty"`

	// The list of elliptic curves to support. Caddy's
	// defaults are modern and secure.
	Curves []string `json:"curves,omitempty"`
}

// MakeTLSClientConfig returns a tls.Config usable by a client to a backend.
// If there is no custom TLS configuration, a nil config may be returned.
func (t *TLSConfig) MakeTLSClientConfig(ctx caddy.Context) (*tls.Config, error) {
	cfg := new(tls.Config)

	// client auth
	if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile == "" {
		return nil, fmt.Errorf("client_certificate_file specified without client_certificate_key_file")
	}
	if t.ClientCertificateFile == "" && t.ClientCertificateKeyFile != "" {
		return nil, fmt.Errorf("client_certificate_key_file specified without client_certificate_file")
	}
	if t.ClientCertificateFile != "" && t.ClientCertificateKeyFile != "" {
		cert, err := tls.LoadX509KeyPair(t.ClientCertificateFile, t.ClientCertificateKeyFile)
		if err != nil {
			return nil, fmt.Errorf("loading client certificate key pair: %v", err)
		}
		cfg.Certificates = []tls.Certificate{cert}
	}
	if t.ClientCertificateAutomate != "" {
		// TODO: use or enable ctx.IdentityCredentials() ...
		tlsAppIface, err := ctx.App("tls")
		if err != nil {
			return nil, fmt.Errorf("getting tls app: %v", err)
		}
		tlsApp := tlsAppIface.(*caddytls.TLS)
		err = tlsApp.Manage(map[string]struct{}{t.ClientCertificateAutomate: {}})
		if err != nil {
			return nil, fmt.Errorf("managing client certificate: %v", err)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Add the corresponding client_certificate_file directive with the client certificate PEM.
  2. Run caddy validate --config to catch the pairing error before touching the running instance.
  3. If migrating to automated certificates, delete the key file directive entirely rather than leaving it set.

Example fix

// before (Caddyfile)
transport http {
    tls {
        client_certificate_key_file /etc/certs/client.key
    }
}

// after
transport http {
    tls {
        client_certificate_file /etc/certs/client.pem
        client_certificate_key_file /etc/certs/client.key
    }
}
Defensive patterns

Strategy: validation

Validate before calling

func validateClientCertPair(certFile, keyFile string) error {
    if certFile == "" && keyFile != "" {
        return fmt.Errorf("client_certificate_key_file specified without client_certificate_file")
    }
    return nil
}

Prevention

When it happens

Trigger: transport http { tls { client_certificate_key_file /etc/certs/client.key } } with the certificate line missing; JSON with only "client_certificate_key_file" populated.

Common situations: Swapped or half-edited config after splitting a combined PEM into cert/key files; templating that emits the key but silently drops the cert line; leftover key directive after switching to client_certificate_automate without deleting the old line.

Understand the failure class

Related errors


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