{"id":"7c6a3f790d240139","repo":"docker/cli","slug":"the-external-ca-option-needs-a-protocol-parameter","errorCode":null,"errorMessage":"the external-ca option needs a protocol= parameter","messagePattern":"the external-ca option needs a protocol= parameter","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/command/swarm/opts.go","lineNumber":210,"sourceCode":"\t\tcase \"url\":\n\t\t\thasURL = true\n\t\t\texternalCA.URL = value\n\t\tcase \"cacert\":\n\t\t\tcacontents, err := os.ReadFile(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"unable to read CA cert for external CA: %w\", err)\n\t\t\t}\n\t\t\tif pemBlock, _ := pem.Decode(cacontents); pemBlock == nil {\n\t\t\t\treturn nil, errors.New(\"CA cert for external CA must be in PEM format\")\n\t\t\t}\n\t\t\texternalCA.CACert = string(cacontents)\n\t\tdefault:\n\t\t\texternalCA.Options[key] = value\n\t\t}\n\t}\n\n\tif !hasProtocol {\n\t\treturn nil, errors.New(\"the external-ca option needs a protocol= parameter\")\n\t}\n\tif !hasURL {\n\t\treturn nil, errors.New(\"the external-ca option needs a url= parameter\")\n\t}\n\n\treturn &externalCA, nil\n}\n\nfunc addSwarmCAFlags(flags *pflag.FlagSet, options *swarmCAOptions) {\n\tflags.DurationVar(&options.nodeCertExpiry, flagCertExpiry, 90*24*time.Hour, \"Validity period for node certificates (ns|us|ms|s|m|h)\")\n\tflags.Var(&options.externalCA, flagExternalCA, \"Specifications of one or more certificate signing endpoints\")\n}\n\nfunc addSwarmFlags(flags *pflag.FlagSet, options *swarmOptions) {\n\tflags.Int64Var(&options.taskHistoryLimit, flagTaskHistoryLimit, 5, \"Task history retention limit\")\n\tflags.DurationVar(&options.dispatcherHeartbeat, flagDispatcherHeartbeat, 5*time.Second, \"Dispatcher heartbeat period (ns|us|ms|s|m|h)\")\n\tflags.Uint64Var(&options.maxSnapshots, flagMaxSnapshots, 0, \"Number of additional Raft snapshots to retain\")\n\tflags.SetAnnotation(flagMaxSnapshots, \"version\", []string{\"1.25\"})","sourceCodeStart":192,"sourceCodeEnd":228,"githubUrl":"https://github.com/docker/cli/blob/e9452d6e785f6e365712b9d71bd7517591773c86/cli/command/swarm/opts.go#L192-L228","documentation":"Returned by parseExternalCA when an `--external-ca` specification is parsed and no `protocol=` key was present among its comma-separated key=value fields. The protocol (currently only \"cfssl\" is recognized) is mandatory because the swarm manager must know how to talk to the external signing endpoint.","triggerScenarios":"`docker swarm init` or `docker swarm update` with `--external-ca` where the CSV spec omits protocol, e.g. `--external-ca url=https://ca.example.com` or a spec where the protocol field was mangled by shell quoting so it never parsed as `protocol=...`.","commonSituations":"Copy-pasting a partial example from docs; shell splitting on commas so only the first field reaches the flag; templated deployment scripts leaving the protocol variable empty.","solutions":["Add `protocol=cfssl` to the spec: `--external-ca protocol=cfssl,url=https://ca.example.com`.","Quote the whole spec so the shell doesn't split it: `--external-ca \"protocol=cfssl,url=...\"`.","Note cfssl is the only accepted protocol value; anything else fails with 'unrecognized external CA protocol'."],"exampleFix":"# before\ndocker swarm update --external-ca url=https://ca.example.com\n# after\ndocker swarm update --external-ca protocol=cfssl,url=https://ca.example.com","handlingStrategy":null,"validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":["docker","swarm","external-ca","cli-flags","validation"],"analyzedSha":"e9452d6e785f6e365712b9d71bd7517591773c86","analyzedAt":"2026-08-01T07:09:22.474Z","schemaVersion":2}