bytebase/bytebase · error

start TLS: %v

Error message

start TLS: %v

What it means

In START_TLS mode, after the plain connection succeeds, dial upgrades it with conn.StartTLS(tlsConfig). If the TLS handshake fails, the connection is closed and the error is wrapped as 'start TLS: %v'. Common root causes are certificate trust and protocol mismatch issues on the port serving StartTLS.

Source

Thrown at backend/plugin/idp/ldap/ldap.go:108

		InsecureSkipVerify: p.config.SkipTLSVerify,
	}
	switch p.config.SecurityProtocol {
	case storepb.LDAPIdentityProviderConfig_LDAPS:
		url := fmt.Sprintf("ldaps://%s:%d", p.config.Host, p.config.Port)
		conn, err := ldap.DialURL(url, ldap.DialWithTLSConfig(tlsConfig))
		if err != nil {
			return nil, errors.Errorf("dial TLS: %v", err)
		}
		return conn, nil
	case storepb.LDAPIdentityProviderConfig_START_TLS:
		url := fmt.Sprintf("ldap://%s:%d", p.config.Host, p.config.Port)
		conn, err := ldap.DialURL(url)
		if err != nil {
			return nil, errors.Errorf("dial: %v", err)
		}
		if err := conn.StartTLS(tlsConfig); err != nil {
			_ = conn.Close()
			return nil, errors.Errorf("start TLS: %v", err)
		}
		return conn, nil
	default:
		url := fmt.Sprintf("ldap://%s:%d", p.config.Host, p.config.Port)
		conn, err := ldap.DialURL(url)
		if err != nil {
			return nil, errors.Errorf("dial: %v", err)
		}
		return conn, nil
	}
}

// Connect establishes a connection using the bind DN and bind password.
func (p *IdentityProvider) Connect() (*ldap.Conn, error) {
	conn, err := p.dial()
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to 1870550677)

Solutions

  1. Load the directory server's CA certificate into the TLS config's RootCAs.
  2. Set ServerName in the TLS config (or fix Host) so it matches the certificate.
  3. Check the certificate expiry and renew if needed.
  4. Align MinVersion/MaxVersion and cipher suites on the TLS config with the server's requirements.
  5. Debug with openssl s_client -starttls ldap -connect host:389 to see the handshake failure.

Example fix

// before
tlsConfig := &tls.Config{} // no RootCAs, self-signed server cert
// after
tlsConfig := &tls.Config{RootCAs: caPool, ServerName: "ldap.example.com"}
Defensive patterns

Strategy: try-catch

Validate before calling

func startTLSHandshakeOK(host string, port int, caPool *x509.CertPool) error {
	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), 5*time.Second)
	if err != nil { return err }
	defer conn.Close()
	tc := tls.Client(conn, &tls.Config{RootCAs: caPool, ServerName: host})
	return tc.Handshake()
}
// pre-flight before Connect

Try / catch

conn, err := p.dial()
if err != nil {
	if strings.Contains(err.Error(), "start TLS") {
		return nil, fmt.Errorf("StartTLS handshake failed for %s:%d (check CA cert/hostname): %w", p.config.Host, p.config.Port, err)
	}
	return nil, err
}

Prevention

When it happens

Trigger: Calling Connect with SecurityProtocol START_TLS where the server's certificate is untrusted (self-signed or internal CA not in tlsConfig.RootCAs), the certificate hostname does not match config.Host, or the negotiated TLS version/ciphers are unsupported.

Common situations: Corporate LDAP servers with internal CA certificates not distributed to clients; expired server certificates; servers requiring TLS 1.2+ while the client offers older versions; pointing StartTLS at an LDAPS-only port.

Understand the failure class

Related errors


AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06). Data as JSON: /api/errors/dc6a8dcfe76da4b5. Report an issue: GitHub.