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

  1. Provide a PEM-formatted CA certificate path in the `cacert=` field.
  2. Convert if needed: `openssl x509 -inform der -in ca.der -out ca.pem`.
  3. 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

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


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)