docker/cli · error
unrecognized external CA protocol
Error message
unrecognized external CA protocol %s
What it means
Raised by parseExternalCA when the protocol= field is not 'cfssl' (case-insensitive). The only supported ExternalCAProtocol value is swarm.ExternalCAProtocolCFSSL; any other value is rejected.
Solutions
- Use protocol=cfssl, which is the only supported external CA protocol in this version.
- Point the cfssl URL at your own signing service that speaks the cfssl API.
- If you need a different CA integration, use a sidecar that exposes the cfssl protocol.
Example fix
# before docker swarm init --external-ca protocol=vault,url=https://vault:8200 # after docker swarm init --external-ca protocol=cfssl,url=https://my-cfssl-signer:8888
Defensive patterns
Strategy: validation
Validate before calling
// Validate protocol before parse
if !strings.EqualFold(proto, string(swarm.ExternalCAProtocolCFSSL)) {
return fmt.Errorf("unsupported external CA protocol %q; only cfssl", proto)
} Type guard
func isSupportedCAProtocol(s string) bool {
return strings.EqualFold(s, string(swarm.ExternalCAProtocolCFSSL))
} Prevention
- Use protocol=cfssl only.
- Front any non-cfssl CA with a cfssl-protocol adapter.
- Track supported protocols per CLI version.
When it happens
Trigger: Passing '--external-ca protocol=vault,url=...' or protocol=https, etc. Only 'cfssl' is accepted at opts.go:187-191.
Common situations: Operator assumes HashiCorp Vault or a generic HTTPS CA is supported directly; case mismatch is not the issue (it's lowercased), only the value matters.
Related errors
- CA cert for external CA must be in PEM format
- the external-ca option needs a protocol= parameter
- the external-ca option needs a url= parameter
- rotating to an external CA requires the
- invalid field ' ' must be a key=value pair
AI-assisted analysis of docker/cli@4f84911bfe (2026-08-07).
Data as JSON: /api/errors/38191d23749f437f.
Report an issue: GitHub.
Appendix: source
Thrown at cli/command/swarm/opts.go:190
var (
hasProtocol bool
hasURL bool
)
for _, field := range fields {
key, value, ok := strings.Cut(field, "=")
if !ok {
return nil, fmt.Errorf("invalid field '%s' must be a key=value pair", field)
}
// 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
}
}
View on GitHub (pinned to 4f84911bfe)