docker/cli · error
CA cert for external CA must be in PEM format
Error message
CA cert for external CA must be in PEM format
What it means
Thrown by parseExternalCA (cli/command/swarm/opts.go:201) when an `--external-ca` spec's `cacert=` sub-field points at a file whose contents fail pem.Decode. This validates the CA certificate pinned to an external CA endpoint, distinct from the root-CA `--ca-cert` path.
Solutions
- Provide a PEM-formatted CA certificate path in the `cacert=` field.
- Convert if needed: `openssl x509 -inform der -in ca.der -out ca.pem`.
- Validate the file with `openssl x509 -in ca.pem -noout` before using it.
Example fix
// before docker swarm update --external-ca protocol=cfssl,url=https://ca:12381,cacert=/etc/ca.der // after openssl x509 -inform der -in /etc/ca.der -out /etc/ca.pem docker swarm update --external-ca protocol=cfssl,url=https://ca:12381,cacert=/etc/ca.pem
Defensive patterns
Strategy: validation
Validate before calling
// Validate the cacert= file in an external-ca spec.
if caPath != "" {
b, err := os.ReadFile(caPath)
if err != nil { return err }
if block, _ := pem.Decode(b); block == nil {
return errors.New("external-ca cacert must be PEM")
}
} Type guard
func isPEM(b []byte) bool {
block, _ := pem.Decode(b)
return block != nil
} Prevention
- Export CA certs from your PKI tool in PEM form.
- Validate the cacert file with openssl before rotate/update.
- Centralize external-CA spec building in one tested helper.
When it happens
Trigger: Passing `--external-ca protocol=cfssl,url=https://ca,cacert=/path/to/non-pem` where the cacert file is not PEM-formatted.
Common situations: Pointing cacert at a DER file or wrong artifact; copy-paste error in the path; cert exported from a CA tool in non-PEM form.
Related errors
- file contents must be in PEM format
- the external-ca option needs a protocol= parameter
- the external-ca option needs a url= parameter
- other flags may not be combined with --rollback
- duplicate mount target
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/5330930cf15e12c2.
Report an issue: GitHub.
Appendix: source
Thrown at cli/command/swarm/opts.go:201
// TODO(thaJeztah): these options should not be case-insensitive.
switch strings.ToLower(key) {
case "protocol":
hasProtocol = true
if strings.ToLower(value) == string(swarm.ExternalCAProtocolCFSSL) {
externalCA.Protocol = swarm.ExternalCAProtocolCFSSL
} else {
return nil, fmt.Errorf("unrecognized external CA protocol %s", value)
}
case "url":
hasURL = true
externalCA.URL = value
case "cacert":
cacontents, err := os.ReadFile(value)
if err != nil {
return nil, fmt.Errorf("unable to read CA cert for external CA: %w", err)
}
if pemBlock, _ := pem.Decode(cacontents); pemBlock == nil {
return nil, errors.New("CA cert for external CA must be in PEM format")
}
externalCA.CACert = string(cacontents)
default:
externalCA.Options[key] = value
}
}
if !hasProtocol {
return nil, errors.New("the external-ca option needs a protocol= parameter")
}
if !hasURL {
return nil, errors.New("the external-ca option needs a url= parameter")
}
return &externalCA, nil
}
func addSwarmCAFlags(flags *pflag.FlagSet, options *swarmCAOptions) {View on GitHub (pinned to 4f84911bfe)