caddyserver/caddy · error

no client certificate provided

Error message

no client certificate provided

What it means

LeafCertClientAuth.VerifyClientCertificate is the callback Go TLS invokes with the peer's raw certificate chain; if the slice is empty (no certificate was presented) it fails with this error. In practice it fires when the TLS mode let the handshake reach verification without a client cert — e.g. mode request combined with a leaf verifier, or a client that sent an empty Certificate message.

Source

Thrown at modules/caddytls/connpolicy.go:1033

		mod, err := caddyfile.UnmarshalModule(d, "tls.leaf_cert_loader."+modName)
		if err != nil {
			return d.WrapErr(err)
		}
		vMod, ok := mod.(LeafCertificateLoader)
		if !ok {
			return fmt.Errorf("leaf module '%s' is not a leaf certificate loader", vMod)
		}
		l.LeafCertificateLoadersRaw = append(
			l.LeafCertificateLoadersRaw,
			caddyconfig.JSONModuleObject(vMod, "loader", modName, nil),
		)
	}
	return nil
}

func (l LeafCertClientAuth) VerifyClientCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
	if len(rawCerts) == 0 {
		return fmt.Errorf("no client certificate provided")
	}

	remoteLeafCert, err := x509.ParseCertificate(rawCerts[0])
	if err != nil {
		return fmt.Errorf("can't parse the given certificate: %s", err.Error())
	}

	if slices.ContainsFunc(l.trustedLeafCerts, remoteLeafCert.Equal) {
		return nil
	}

	return fmt.Errorf("client leaf certificate failed validation")
}

// PublicKeyAlgorithm is a JSON-unmarshalable wrapper type.
type PublicKeyAlgorithm x509.PublicKeyAlgorithm

// UnmarshalJSON satisfies json.Unmarshaler.

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Send a client certificate: curl --cert client.pem --key client.key https://host/
  2. Align the mode with your intent: use require_and_verify so missing certs are rejected earlier and clearly
  3. Confirm the client actually loads its cert (check client-side logs; many tools warn when a cert file fails to parse)
  4. If some clients legitimately have no cert, use verify_if_given and ensure your verifier tolerates that path per current Caddy semantics

Example fix

# before
curl https://mtls.example.com/

# after
curl --cert client.pem --key client.key https://mtls.example.com/
Defensive patterns

Strategy: validation

Validate before calling

// Client-side: verify you actually have a cert+key pair before connecting
func canPresentCert(certFile, keyFile string) error {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return fmt.Errorf("client cert unusable: %w", err)
	}
	_ = cert
	return nil
}

Try / catch

// Server-side logging: distinguish missing vs untrusted client certs
if err := conn.VerifyConnection(...); err != nil { /* caddy logs it */ }
// In access logs / metrics, alert on TLS handshake failures per client IP to catch cert-less clients

Prevention

When it happens

Trigger: client_auth mode 'request' (or verify_if_given) with a leaf verifier: the client sends no certificate and Go still calls the custom verifier path in some configurations; a client connecting without any cert configured when verification is active.

Common situations: Testing mTLS endpoints with plain curl (no --cert) while a leaf verifier is configured; clients with a cert that fails to load and silently send none; policy mismatch between mode and verifiers.

Understand the failure class

Related errors


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