caddyserver/caddy · error
client_certificate_file specified without client_certificate
Error message
client_certificate_file specified without client_certificate_key_file
What it means
In the transport's tls block, client_certificate_file and client_certificate_key_file are a required pair for mTLS to the upstream. MakeTLSClientConfig validates the pairing first and returns this error when a cert file is set but the key file is empty, so no tls.Config is produced and provisioning fails.
Source
Thrown at modules/caddyhttp/reverseproxy/httptransport.go:795
// attempted even if it is configured. Handy when using dynamic upstreams that
// return HTTP and HTTPS endpoints too.
// When specified, TLS will automatically be configured on the transport.
// 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)View on GitHub (pinned to 50e54ee279)
Solutions
- Add the matching client_certificate_key_file pointing at the PEM/TLS key that pairs with the certificate.
- Verify both paths exist and are readable before reload (caddy validate --config).
- If you actually want an automated certificate, remove client_certificate_file and use client_certificate_automate instead (different mechanism, no key file needed).
Example fix
// before (Caddyfile)
transport http {
tls {
client_certificate_file /etc/certs/client.pem
}
}
// 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_file specified without client_certificate_key_file")
}
return nil
} Prevention
- Always author cert and key directives as an adjacent pair in templates.
- Include both fields or neither in generated JSON.
- Run caddy validate in CI for every config artifact.
When it happens
Trigger: transport http { tls { client_certificate_file /etc/certs/client.pem } } with no client_certificate_key_file; JSON "client_certificate_file" set while "client_certificate_key_file" is omitted or empty string.
Common situations: Copy-pasting half of an mTLS example; assuming the key lives beside the cert and will be inferred; JSON generated from a template where the key field is conditionally omitted; renaming fields during config migration so one name resolves empty.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- client_certificate_key_file specified without client_certifi
- if HTTP/3 is enabled to the upstream, no other HTTP versions
- parsing CIDR expression: '%s': %v
- client auth mode not recognized: %s
- automation policy %d is the second policy that acts as defau
AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15).
Data as JSON: /api/errors/bdd973759348af31.
Report an issue: GitHub.