snail007/goproxy · error

failed to parse root certificate

Error message

failed to parse root certificate

What it means

getRequestTlsConfig builds a tls.Config for an outgoing TLS connection to the proxy parent. After loading PEM bytes it calls x509 CertPool.AppendCertsFromPEM, and this error is thrown when the returned ok flag is false, i.e. none of the supplied bytes could be parsed as a PEM-encoded certificate. The library requires a valid root/CA certificate file to authenticate the proxy, so it aborts instead of silently skipping verification (InsecureSkipVerify is false).

Source

Thrown at utils/functions.go:134

	if err != nil {
		return
	}
	_conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), time.Duration(timeout)*time.Millisecond)
	if err != nil {
		return
	}
	return *tls.Client(_conn, conf), err
}
func getRequestTlsConfig(certBytes, keyBytes []byte) (conf *tls.Config, err error) {
	var cert tls.Certificate
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	if err != nil {
		return
	}
	serverCertPool := x509.NewCertPool()
	ok := serverCertPool.AppendCertsFromPEM(certBytes)
	if !ok {
		err = errors.New("failed to parse root certificate")
	}
	conf = &tls.Config{
		RootCAs:            serverCertPool,
		Certificates:       []tls.Certificate{cert},
		ServerName:         "proxy",
		InsecureSkipVerify: false,
	}
	return
}

func ConnectHost(hostAndPort string, timeout int) (conn net.Conn, err error) {
	conn, err = net.DialTimeout("tcp", hostAndPort, time.Duration(timeout)*time.Millisecond)
	return
}
func ListenTls(ip string, port int, certBytes, keyBytes []byte) (ln *net.Listener, err error) {
	var cert tls.Certificate
	cert, err = tls.X509KeyPair(certBytes, keyBytes)
	if err != nil {

View on GitHub (pinned to e6d6a821db)

Solutions

  1. Regenerate or re-export the CA certificate in PEM format (openssl x509 -in cert.der -out ca.pem -outform PEM) and point the config at that file
  2. Verify the file actually contains PEM blocks: head the file and confirm '-----BEGIN CERTIFICATE-----' lines exist and it is not empty or a key
  3. Check the config path for typos and that the process has read permission on the file
  4. As a last resort for testing only, run with verification skipped (insecure mode), but never in production

Example fix

// before (certBytes from a DER file -> AppendCertsFromPEM returns false)
ok := serverCertPool.AppendCertsFromPEM(certBytes)
// after: ensure the file is PEM on disk
// openssl x509 -in ca.crt -out ca.pem -outform PEM
// then start with ca.pem configured
ok := serverCertPool.AppendCertsFromPEM(certBytes) // ok == true
Defensive patterns

Strategy: validation

Validate before calling

func validPEM(path string) error {
    b, err := os.ReadFile(path)
    if err != nil { return err }
    if !bytes.Contains(b, []byte("-----BEGIN CERTIFICATE-----")) {
        return fmt.Errorf("%s is not a PEM certificate", path)
    }
    return nil
}
// call before starting the service: validPEM(cfg.CertPath)

Try / catch

if err := startService(cfg); err != nil {
    if strings.Contains(err.Error(), "failed to parse root certificate") {
        // surface config guidance: check CA file is PEM
    }
}

Prevention

When it happens

Trigger: The certificate file configured for the TLS parent points to a missing, empty, non-PEM, or corrupt file; AppendCertsFromPEM returns false when certBytes contains no parseable PEM CERTIFICATE blocks, so getRequestTlsConfig constructs the error and TlsConnect fails.

Common situations: Config pointing root CA path at the wrong file or a private key instead of a certificate; certificate generated in DER format instead of PEM; file truncated by a failed copy/mount; using an expired or malformed cert generated by a broken tooling pipeline.

Understand the failure class


AI-assisted analysis of snail007/goproxy@e6d6a821db (2026-09-03). Data as JSON: /api/errors/411f6ad9f4d6bb2d. Report an issue: GitHub.