Billionmail/BillionMail · critical

Failed to generate user private key: {}

Error message

Failed to generate user private key: {}

What it means

GetMyUser loads the persisted ACME account ECDSA key, and if none exists generates a new P-256 key with ecdsa.GenerateKey. If crypto/rand fails, the error is wrapped via public.LangCtx into this localized message and returned, aborting the SSL flow (ApplySSLWithExistingServer) before any ACME client is created.

Source

Thrown at core/internal/service/acme/acme.go:77

	if public.FileExists(accountKeyPath) {
		keyBytes, readErr := public.ReadFile(accountKeyPath)
		if readErr == nil {
			block, _ := pem.Decode([]byte(keyBytes))
			if block != nil {
				parsedKey, parseErr := x509.ParseECPrivateKey(block.Bytes)
				if parseErr == nil {
					privateKey = parsedKey
				}
			}
		}
	}

	if privateKey == nil {
		// Generate new private key if none exists
		privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
		if err != nil {
			return nil, errors.New(public.LangCtx(ctx, "Failed to generate user private key: {}", err.Error()))
		}

		// Persist the key for future use
		keyBytes, marshalErr := x509.MarshalECPrivateKey(privateKey)
		if marshalErr == nil {
			pemBlock := &pem.Block{
				Type:  "EC PRIVATE KEY",
				Bytes: keyBytes,
			}
			pemBytes := pem.EncodeToMemory(pemBlock)

			dir := filepath.Dir(accountKeyPath)
			if !public.FileExists(dir) {
				os.MkdirAll(dir, 0750)
			}
			public.WriteFile(accountKeyPath, string(pemBytes))
		}
	}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Check the container's seccomp/apparmor profile allows getrandom(2); loosen the profile or use a default Docker profile.
  2. Verify /dev/urandom exists and is readable inside the container/host.
  3. Inspect core/data/acme/account.key — if it exists but is corrupt, delete it so a fresh key is generated and persisted.
  4. Upgrade the kernel/glibc on minimal hosts where getrandom is broken.

Example fix

// before
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
	return nil, errors.New(public.LangCtx(ctx, "Failed to generate user private key: {}", err.Error()))
}
// after
privateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
	return nil, fmt.Errorf("generate ACME account key (check crypto/rand availability/seccomp): %w", err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight before ApplySSL
f, err := os.OpenFile("/dev/urandom", os.O_RDONLY, 0)
if err != nil { return fmt.Errorf("entropy source unavailable: %w", err) }
f.Close()

Type guard

func hasUsableAccountKey(pemBytes []byte) bool {
	block, _ := pem.Decode(pemBytes)
	if block == nil { return false }
	_, err := x509.ParseECPrivateKey(block.Bytes)
	return err == nil
}

Try / catch

u, err := acme.GetMyUser(ctx, email)
if err != nil {
	if strings.Contains(err.Error(), "Failed to generate user private key") {
		return fmt.Errorf("crypto/rand unavailable — check container seccomp/dev/urandom: %w", err)
	}
	return err
}

Prevention

When it happens

Trigger: ecdsa.GenerateKey returning an error — practically only when the OS cryptographic entropy source (/dev/urandom, getrandom) fails: sandboxed/restricted containers, seccomp filters blocking getrandom, or a heavily degraded kernel.

Common situations: Container with restricted seccomp profile blocking getrandom(2); unusual minimal distros lacking proper /dev/urandom; this message also appears when the account.key file could not be read/parsed (silently ignored) and generation then runs — check for a corrupted PEM file first.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/b12265554d979e75. Report an issue: GitHub.