caddyserver/caddy · error

access control %d public key %d: parsing base64 certificate

Error message

access control %d public key %d: parsing base64 certificate DER: %v

What it means

When enabling the remote admin endpoint, Caddy decodes each base64-DER client certificate listed under admin.remote.access_control[].public_keys to build the client CA pool. If any entry is not valid base64 or not a parseable DER certificate, this error reports which access control (i) and key (j) failed, wrapping the x509/base64 cause.

Source

Thrown at admin.go:552

	if err != nil {
		return err
	}

	// make the HTTP handler but disable Host/Origin enforcement
	// because we are using TLS authentication instead
	handler, err := cfg.Admin.newAdminHandler(addr, true, ctx)
	if err != nil {
		return err
	}

	// create client certificate pool for TLS mutual auth, and extract public keys
	// so that we can enforce access controls at the application layer
	clientCertPool := x509.NewCertPool()
	for i, accessControl := range cfg.Admin.Remote.AccessControl {
		for j, certBase64 := range accessControl.PublicKeys {
			cert, err := decodeBase64DERCert(certBase64)
			if err != nil {
				return fmt.Errorf("access control %d public key %d: parsing base64 certificate DER: %v", i, j, err)
			}
			accessControl.publicKeys = append(accessControl.publicKeys, cert.PublicKey)
			clientCertPool.AddCert(cert)
		}
	}

	// create TLS config that will enforce mutual authentication
	if identityCertCache == nil {
		return fmt.Errorf("cannot enable remote admin without a certificate cache; configure identity management to initialize a certificate cache")
	}
	cmCfg := cfg.Admin.Identity.certmagicConfig(remoteLogger, false)
	tlsConfig := cmCfg.TLSConfig()
	tlsConfig.NextProtos = nil // this server does not solve ACME challenges
	tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
	tlsConfig.ClientCAs = clientCertPool

	// convert logger to stdlib so it can be used by HTTP server
	serverLogger, err := zap.NewStdLogAt(remoteLogger, zap.DebugLevel)

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Produce the correct value: openssl x509 -in client.pem -outform DER | base64 -w0
  2. Check the indicated indices (access control %d, public key %d) in your config against the error to find the exact bad entry
  3. Remove whitespace/newlines from the base64 string
  4. Verify the DER parses: openssl x509 -inform DER -in client.der -noout

Example fix

# before: base64 of PEM text (wrong)
public_keys: ["LS0tLS1CRUdJTiBDRVJUSUZ..."]

# after: base64 of DER bytes
openssl x509 -in client.pem -outform DER | base64 -w0
public_keys: ["MIIBvzCCAYWgAwIBAgIU..."]
Defensive patterns

Strategy: validation

Validate before calling

func toBase64DER(pemCert []byte) (string, error) {
	block, _ := pem.Decode(pemCert)
	if block == nil {
		return "", errors.New("input is not PEM")
	}
	if _, err := x509.ParseCertificate(block.Bytes); err != nil {
		return "", fmt.Errorf("not a certificate: %v", err)
	}
	return base64.StdEncoding.EncodeToString(block.Bytes), nil
}

Prevention

When it happens

Trigger: admin.remote config where a public_keys entry is base64 of PEM instead of DER; truncated or whitespace-mangled base64; a certificate encoded with standard base64 including '=' padding the decoder rejects, or simply not a certificate at all.

Common situations: Converting a PEM client cert to base64 without first converting to DER (base64 of the PEM text); copy-paste truncation; exporting the public key bytes instead of the full certificate; line-wrapped base64 pasted with newlines.

Understand the failure class

Related errors


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