{"record":{"id":"ef6b39c056b0fcbf","repo":"netbirdio/netbird","slug":"both-cert-file-and-cert-key-must-be-provided-when","errorCode":null,"errorMessage":"both cert-file and cert-key must be provided when not using LetsEncrypt","messagePattern":"both cert-file and cert-key must be provided when not using LetsEncrypt","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"signal/cmd/run.go","lineNumber":231,"sourceCode":"\t\ttlsConfig   *tls.Config\n\t)\n\n\tif signalLetsencryptDomain == \"\" && signalCertFile == \"\" && signalCertKey == \"\" {\n\t\tlog.Infof(\"running without TLS\")\n\t\treturn nil, nil, nil, nil\n\t}\n\n\tif signalLetsencryptDomain != \"\" {\n\t\tcertManager, err = encryption.CreateCertManager(signalLetsencryptDataDir, signalLetsencryptDomain)\n\t\tif err != nil {\n\t\t\treturn nil, certManager, nil, err\n\t\t}\n\t\ttlsConfig = certManager.TLSConfig()\n\t\tlog.Infof(\"setting up TLS with LetsEncrypt.\")\n\t} else {\n\t\tif signalCertFile == \"\" || signalCertKey == \"\" {\n\t\t\tlog.Errorf(\"both cert-file and cert-key must be provided when not using LetsEncrypt\")\n\t\t\treturn nil, certManager, nil, errors.New(\"both cert-file and cert-key must be provided when not using LetsEncrypt\")\n\t\t}\n\n\t\ttlsConfig, err = loadTLSConfig(signalCertFile, signalCertKey)\n\t\tif err != nil {\n\t\t\tlog.Errorf(\"cannot load TLS credentials: %v\", err)\n\t\t\treturn nil, certManager, nil, err\n\t\t}\n\t\tlog.Infof(\"setting up TLS with custom certificates.\")\n\t}\n\n\ttransportCredentials := credentials.NewTLS(tlsConfig)\n\n\treturn []grpc.ServerOption{grpc.Creds(transportCredentials)}, certManager, tlsConfig, err\n}\n\nfunc startServerWithCertManager(certManager *autocert.Manager, grpcRootHandler http.Handler) {\n\t// a call to certManager.Listener() always creates a new listener so we do it once\n\thttpListener := certManager.Listener()","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/signal/cmd/run.go#L213-L249","documentation":"The signal server's TLS setup requires certificate material before it can create gRPC transport credentials. When --letsencrypt-domain is empty, the code demands both --cert-file and --cert-key; if either flag is missing or empty it logs and returns this error, so `signal run` aborts before the listener starts. It is a deliberate startup validation, not a runtime failure.","triggerScenarios":"Running `signal run` with neither --letsencrypt-domain nor the cert pair; supplying only one of --cert-file / --cert-key; one flag resolving to an empty string because a docker-compose env var or systemd unit referenced a secret that did not mount.","commonSituations":"Migrating a signal deployment from plain HTTP to TLS and forgetting the key flag; Kubernetes/Docker secret mounting only one of the two PEM files; copying a compose template that omits both flags.","solutions":["Pass both flags: --cert-file /path/tls.crt --cert-key /path/tls.key with valid PEM files.","Or switch to LetsEncrypt: --letsencrypt-domain signal.example.com with --letsencrypt-data-dir writable.","Verify the flags actually reach the process (inspect `ps aux` or the unit file) and that both mounted files are non-empty."],"exampleFix":"# before\nsignal run --log-level debug\n\n# after (custom certs)\nsignal run --cert-file /etc/certs/tls.crt --cert-key /etc/certs/tls.key\n\n# or (LetsEncrypt)\nsignal run --letsencrypt-domain signal.example.com --letsencrypt-data-dir /var/lib/signal/certs","handlingStrategy":"validation","validationCode":"// Before starting the signal server, validate the TLS flag combination\nif letsencryptDomain == \"\" && (certFile == \"\" || certKey == \"\") {\n    return errors.New(\"provide both --cert-file and --cert-key, or set --letsencrypt-domain\")\n}","typeGuard":null,"tryCatchPattern":"if err := runServer(...); err != nil {\n    if strings.Contains(err.Error(), \"both cert-file and cert-key\") {\n        // config problem: fix flags, do not retry\n    }\n    return err\n}","preventionTips":["Encode the flag pair in deployment templates (compose/systemd) so one is never added without the other.","Add a startup smoke check that fails fast on empty-string flag values caused by missing secrets.","Consider LetsEncrypt mode for single-domain deployments to remove manual cert handling."],"tags":["tls","configuration","signal","startup","certificates"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}