siyuan-note/siyuan · error

failed to write CA private key: %w

Error message

failed to write CA private key: %w

What it means

Returned by ImportCABundle when os.WriteFile fails writing caKeyPEM to <ConfDir>/ca.key (mode 0600). The CA cert has already been written by this point, so a failure here leaves ca.crt on disk without a matching ca.key — the import is partially applied and the kernel's TLS init will not be able to sign server certs until resolved.

Source

Thrown at kernel/util/cert.go:348

	keyBlock, _ := pem.Decode([]byte(caKeyPEM))
	if keyBlock == nil {
		return fmt.Errorf("failed to decode CA private key PEM")
	}

	_, err = x509.ParseECPrivateKey(keyBlock.Bytes)
	if err != nil {
		return fmt.Errorf("failed to parse CA private key: %w", err)
	}

	caCertPath := filepath.Join(ConfDir, TLSCACertFilename)
	caKeyPath := filepath.Join(ConfDir, TLSCAKeyFilename)

	if err := os.WriteFile(caCertPath, []byte(caCertPEM), 0644); err != nil {
		return fmt.Errorf("failed to write CA certificate: %w", err)
	}

	if err := os.WriteFile(caKeyPath, []byte(caKeyPEM), 0600); err != nil {
		return fmt.Errorf("failed to write CA private key: %w", err)
	}

	certPath := filepath.Join(ConfDir, TLSCertFilename)
	keyPath := filepath.Join(ConfDir, TLSKeyFilename)

	if gulu.File.IsExist(certPath) {
		os.Remove(certPath)
	}
	if gulu.File.IsExist(keyPath) {
		os.Remove(keyPath)
	}

	logging.LogInfof("imported CA bundle, server certificate will be regenerated on next TLS initialization")
	return nil
}

// trimIPv6Brackets removes brackets from IPv6 address strings like "[::1]"
func trimIPv6Brackets(ip string) string {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Remove or fix permissions on any existing <ConfDir>/ca.key (must be writable by the kernel user, ideally 0600).
  2. Ensure ConfDir is writable and has free space before importing.
  3. If ca.crt was written but ca.key failed, re-run ImportCABundle with both PEMs after fixing the FS issue, so the pair is consistent.

Example fix

// before
err := util.ImportCABundle(certPEM, keyPEM) // -> failed to write CA private key: .../ca.key: permission denied

// after
os.Chmod(filepath.Join(util.ConfDir, util.TLSCAKeyFilename), 0600)
err := util.ImportCABundle(certPEM, keyPEM)
Defensive patterns

Strategy: try-catch

Try / catch

if err := util.ImportCABundle(certPEM, keyPEM); err != nil {
    if strings.Contains(err.Error(), "failed to write CA private key") {
        // ca.crt may already be written; fix ca.key perms then re-run both for consistency
        keyPath := filepath.Join(util.ConfDir, util.TLSCAKeyFilename)
        os.Chmod(keyPath, 0600) // or os.Remove(keyPath)
        return util.ImportCABundle(certPEM, keyPEM)
    }
    return err
}

Prevention

When it happens

Trigger: Calling ImportCABundle where writing ca.key fails specifically — e.g. an existing ca.key owned by another user, read-only conf, or ENOSPC after ca.crt was written.

Common situations: Stale ca.key with restrictive permissions from a previous run; conf dir on a nearly-full volume; permission regression after a workspace move.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/b95699d65ae21b0d. Report an issue: GitHub.