slackhq/nebula · critical

failed to parse private key: %s

Error message

failed to parse private key: %s

What it means

SetHostKey parses the SSH host private key with golang.org/x/crypto/ssh.ParsePrivateKey and wraps any parse failure as 'failed to parse private key'. This key is what the nebula SSH debug server presents as its host key. Called from configSSH during config load, so a bad key aborts startup.

Source

Thrown at sshd/server.go:107

		PublicKeyCallback: cc.Authenticate,
		ServerVersion:     fmt.Sprintf("SSH-2.0-Nebula???"),
	}

	s.RegisterCommand(&Command{
		Name:             "help",
		ShortDescription: "prints available commands or help <command> for specific usage info",
		Callback: func(a any, args []string, w StringWriter) error {
			return helpCallback(s.commands, args, w)
		},
	})

	return s, nil
}

func (s *SSHServer) SetHostKey(hostPrivateKey []byte) error {
	private, err := ssh.ParsePrivateKey(hostPrivateKey)
	if err != nil {
		return fmt.Errorf("failed to parse private key: %s", err)
	}

	s.config.AddHostKey(private)
	return nil
}

func (s *SSHServer) ClearTrustedCAs() {
	s.authLock.Lock()
	s.trustedCAs = []ssh.PublicKey{}
	s.authLock.Unlock()
}

func (s *SSHServer) ClearAuthorizedKeys() {
	s.authLock.Lock()
	s.trustedKeys = make(map[string]map[string]bool)
	s.authLock.Unlock()
}

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Generate a supported key: ssh-keygen -t ed25519 -m PEM -N '' -f nebula_host_key (no passphrase)
  2. Point the ssh.host_key config at the private key file, not the .pub file
  3. Decrypt or regenerate the key without a passphrase (ParsePrivateKey does not prompt)
  4. If the key is PKCS#1/OpenSSH-format incompatibility, convert with ssh-keygen -p -m PEM

Example fix

// before
ssh-keygen -t ed25519 -f host_key   # passphrase-protected, parse fails
// after
ssh-keygen -t ed25519 -m PEM -N "" -f host_key
Defensive patterns

Strategy: validation

Validate before calling

# Validate the host key parses before deploying nebula
ssh-keygen -y -f host_key > /dev/null && echo OK
# Or in Go:
if _, err := ssh.ParsePrivateKey(hostKeyBytes); err != nil {
	log.Fatalf("host key invalid: %v", err)
}

Try / catch

try {
  loadConfig(path)
} catch (e) {
  if (e.message.includes("failed to parse private key")) {
    console.error("check ssh.host_key: must be an unencrypted PEM private key, not the .pub file")
  }
  throw e
}

Prevention

When it happens

Trigger: configSSH passes the raw bytes of ssh.host_key (file contents) to SetHostKey and ParsePrivateKey fails: unsupported key format, encrypted (passphrase-protected) key, empty or truncated file, or the config pointed at a public key / non-key file.

Common situations: Generating an RSA key with new OpenSSH format not supported by an older library version; using a passphrase-protected key without decrypting it; pointing host_key at the .pub file; YAML path mistakes yielding empty bytes.

Understand the failure class

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/d2debf1e3432ece8. Report an issue: GitHub.