slackhq/nebula · error
bytes did not contain a proper private key banner
Error message
bytes did not contain a proper private key banner
What it means
pem.Decode succeeded but the block's Type string does not match any recognized private key banner ('NEBULA X25519 PRIVATE KEY' or 'NEBULA ECDSA P256 PRIVATE KEY'). The input is valid PEM, but it is not a nebula private key of the supported kind — often it is a public key, a signing key, or a standard OpenSSL PEM type.
Source
Thrown at cert/pem.go:244
// UnmarshalPrivateKeyFromPEM will try to unmarshal the first pem block in a byte array, returning any non
// consumed data or an error on failure
func UnmarshalPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
k, r := pem.Decode(b)
if k == nil {
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
}
var expectedLen int
var curve Curve
switch k.Type {
case X25519PrivateKeyBanner:
expectedLen = 32
curve = Curve_CURVE25519
case P256PrivateKeyBanner:
expectedLen = 32
curve = Curve_P256
default:
return nil, r, 0, fmt.Errorf("bytes did not contain a proper private key banner")
}
if len(k.Bytes) != expectedLen {
return nil, r, 0, fmt.Errorf("key was not %d bytes, is invalid %s private key", expectedLen, curve)
}
return k.Bytes, r, curve, nil
}
func UnmarshalSigningPrivateKeyFromPEM(b []byte) ([]byte, []byte, Curve, error) {
k, r := pem.Decode(b)
if k == nil {
return nil, r, 0, fmt.Errorf("input did not contain a valid PEM encoded block")
}
var curve Curve
switch k.Type {
case EncryptedEd25519PrivateKeyBanner:
return nil, nil, Curve_CURVE25519, ErrPrivateKeyEncrypted
case EncryptedECDSAP256PrivateKeyBanner:
return nil, nil, Curve_P256, ErrPrivateKeyEncryptedView on GitHub (pinned to dd8f660c0a)
Solutions
- Use the correct file: the entry for tunl.private_key must contain an 'X25519 PRIVATE KEY' or 'ECDSA P256 PRIVATE KEY' nebula banner
- Regenerate the key with nebula-cert keygen so the banner matches what UnmarshalPrivateKeyFromPEM expects
- Check that you are not passing a signing private key (that belongs in signing_key_path and is parsed by UnmarshalSigningPrivateKeyFromPEM)
Example fix
// before (config)
firewall: ...
tunl: { private_key_path: ./signing.key } // wrong key
// after
tunl: { private_key_path: ./host.key } // X25519/ECDSA P256 private key banner Defensive patterns
Strategy: validation
Validate before calling
blk, _ := pem.Decode(data)
if blk != nil && blk.Type != "NEBULA X25519 PRIVATE KEY" && blk.Type != "NEBULA ECDSA P256 PRIVATE KEY" {
return fmt.Errorf("expected a nebula host private key, got PEM type %q", blk.Type)
} Type guard
func isHostPrivateKey(b []byte) bool {
blk, _ := pem.Decode(b)
return blk != nil && (blk.Type == "NEBULA X25519 PRIVATE KEY" || blk.Type == "NEBULA ECDSA P256 PRIVATE KEY")
} Try / catch
key, _, _, err := nebula.UnmarshalPrivateKeyFromPEM(raw)
if err != nil {
return fmt.Errorf("key file has the wrong banner for a host private key: %w", err)
} Prevention
- Keep host keys and signing keys in clearly named separate files
- Cross-check nebula config key_path vs signing_key_path entries before deploying
- Inspect the BEGIN line of each key file once at startup
When it happens
Trigger: Call UnmarshalPrivateKeyFromPEM with a PEM block whose k.Type is not X25519PrivateKeyBanner or P256PrivateKeyBanner, e.g. passing a signing (Ed25519/ECDSA) key PEM, a public key PEM, or a 'RSA PRIVATE KEY' / 'PRIVATE KEY' standard block.
Common situations: Swapping the tunl.private_key and tunl.signing_key_path config entries by mistake, pointing at the certificate/public key file instead of the key file, or using keys generated by openssl in standard PEM formats the library does not accept.
Related errors
- bytes did not contain a proper Ed25519/ECDSA private key ban
- key was not %d bytes, is invalid %s private key
- ErrInvalidPrivateKey
- ErrInvalidPEMX25519PrivateKeyBanner
- ErrInvalidPEMEd25519PublicKeyBanner
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/10d7344eb6028d00.
Report an issue: GitHub.