netbirdio/netbird · error
both cert-file and cert-key must be provided when not using
Error message
both cert-file and cert-key must be provided when not using LetsEncrypt
What it means
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.
Source
Thrown at signal/cmd/run.go:231
tlsConfig *tls.Config
)
if signalLetsencryptDomain == "" && signalCertFile == "" && signalCertKey == "" {
log.Infof("running without TLS")
return nil, nil, nil, nil
}
if signalLetsencryptDomain != "" {
certManager, err = encryption.CreateCertManager(signalLetsencryptDataDir, signalLetsencryptDomain)
if err != nil {
return nil, certManager, nil, err
}
tlsConfig = certManager.TLSConfig()
log.Infof("setting up TLS with LetsEncrypt.")
} else {
if signalCertFile == "" || signalCertKey == "" {
log.Errorf("both cert-file and cert-key must be provided when not using LetsEncrypt")
return nil, certManager, nil, errors.New("both cert-file and cert-key must be provided when not using LetsEncrypt")
}
tlsConfig, err = loadTLSConfig(signalCertFile, signalCertKey)
if err != nil {
log.Errorf("cannot load TLS credentials: %v", err)
return nil, certManager, nil, err
}
log.Infof("setting up TLS with custom certificates.")
}
transportCredentials := credentials.NewTLS(tlsConfig)
return []grpc.ServerOption{grpc.Creds(transportCredentials)}, certManager, tlsConfig, err
}
func startServerWithCertManager(certManager *autocert.Manager, grpcRootHandler http.Handler) {
// a call to certManager.Listener() always creates a new listener so we do it once
httpListener := certManager.Listener()View on GitHub (pinned to 93e97f4bf1)
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.
Example fix
# before signal run --log-level debug # after (custom certs) signal run --cert-file /etc/certs/tls.crt --cert-key /etc/certs/tls.key # or (LetsEncrypt) signal run --letsencrypt-domain signal.example.com --letsencrypt-data-dir /var/lib/signal/certs
Defensive patterns
Strategy: validation
Validate before calling
// Before starting the signal server, validate the TLS flag combination
if letsencryptDomain == "" && (certFile == "" || certKey == "") {
return errors.New("provide both --cert-file and --cert-key, or set --letsencrypt-domain")
} Try / catch
if err := runServer(...); err != nil {
if strings.Contains(err.Error(), "both cert-file and cert-key") {
// config problem: fix flags, do not retry
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- listen_port is required for TLS services
- failed initializing log %v
- startup check: signal not connected
- listen_port is not supported for HTTP services
- domain is required for TCP/UDP services (used for cluster de
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/ef6b39c056b0fcbf.
Report an issue: GitHub.