block/buzz · critical · anyhow::Error
BUZZ_RELAY_PRIVATE_KEY is required when…
Error message
BUZZ_RELAY_PRIVATE_KEY is required when BUZZ_REQUIRE_RELAY_MEMBERSHIP=true. NIP-43 events signed with an ephemeral key become unverifiable after restart.
What it means
Fatal startup error: BUZZ_REQUIRE_RELAY_MEMBERSHIP=true but BUZZ_RELAY_PRIVATE_KEY is unset. Membership enforcement means the relay signs NIP-43 membership events, and events signed with an ephemeral, in-memory key become unverifiable after restart — so the relay refuses to boot without a stable key. The check runs before any DB mutation.
Solutions
- Generate a stable key: openssl rand -hex 32
- Set BUZZ_RELAY_PRIVATE_KEY to that 64-hex value in the relay's environment/secret manager and restart
- Keep the same value across restarts and replicas of the same deployment — rotating it invalidates previously signed NIP-43 events
- For dev without enforcement, BUZZ_REQUIRE_RELAY_MEMBERSHIP=false avoids the requirement
Example fix
# before export BUZZ_REQUIRE_RELAY_MEMBERSHIP=true # BUZZ_RELAY_PRIVATE_KEY unset # after export BUZZ_REQUIRE_RELAY_MEMBERSHIP=true export BUZZ_RELAY_PRIVATE_KEY=$(openssl rand -hex 32) # store durably, reuse every boot
Defensive patterns
Strategy: validation
Validate before calling
# Deploy-time guard when membership enforcement is on:
if [ "${BUZZ_REQUIRE_RELAY_MEMBERSHIP:-false}" = "true" ]; then
: "${BUZZ_RELAY_PRIVATE_KEY:?BUZZ_RELAY_PRIVATE_KEY required when BUZZ_REQUIRE_RELAY_MEMBERSHIP=true}"
fi Prevention
- Generate the key once (openssl rand -hex 32) and store it durably — every restart must reuse it
- Never let the relay fall back to an ephemeral key under NIP-43: signed events become unverifiable
- Keep the key identical across replicas of the same deployment
When it happens
Trigger: Enabling BUZZ_REQUIRE_RELAY_MEMBERSHIP=true without also setting BUZZ_RELAY_PRIVATE_KEY, or the secret failing to reach the process (empty env var from a misconfigured secret mount).
Common situations: Hardening an existing deployment by turning membership enforcement on but forgetting the signing key; Kubernetes secret not mounted; CI env dropped between jobs.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- RELAY_OWNER_PUBKEY required when…
- Cannot derive a community host from BUZZ_RELAY_URL
- Configuration error
- invalid BUZZ_RELAY_PRIVATE_KEY
- Failed to backfill pubkey_allowlist (required when…
AI-assisted analysis of block/buzz@ef2aa1ae38 (2026-08-20).
Data as JSON: /api/errors/49bad9a66c1211bf.
Report an issue: GitHub.
Appendix: source
Thrown at crates/buzz-relay/src/main.rs:333
// NIP-43: if membership enforcement is on, a valid owner pubkey is required.
// config.rs already strips invalid values with a warning; catch the resulting
// None here so we fail fast with a clear message rather than starting a relay
// that no one can administer.
if config.require_relay_membership && config.relay_owner_pubkey.is_none() {
error!(
"BUZZ_REQUIRE_RELAY_MEMBERSHIP=true but RELAY_OWNER_PUBKEY is not set or invalid. \
Set RELAY_OWNER_PUBKEY to a valid 64-char hex pubkey."
);
return Err(anyhow::anyhow!(
"RELAY_OWNER_PUBKEY required when BUZZ_REQUIRE_RELAY_MEMBERSHIP=true"
));
}
// NIP-43: relay membership requires a stable signing key.
// Check this before any DB mutations so we fail fast — no point backfilling
// or bootstrapping if we'll reject the config anyway.
if config.require_relay_membership && config.relay_private_key.is_none() {
return Err(anyhow::anyhow!(
"BUZZ_RELAY_PRIVATE_KEY is required when BUZZ_REQUIRE_RELAY_MEMBERSHIP=true. \
NIP-43 events signed with an ephemeral key become unverifiable after restart."
));
}
// NIP-43 / multi-tenant: seed the deployment's *own* community before any
// membership backfill or owner bootstrap, so those writes are scoped to a
// real `(community_id, pubkey)` and not a global pubkey. The host is derived
// from `relay_url` with the *same* normalization request resolution uses
// (`relay_url_authority` → `normalize_host`), so the bootstrapped owner lands
// in exactly the community that live requests for this host will resolve to.
//
// `ensure_configured_community` is idempotent, so this is safe to run every
// startup. An empty authority (unparseable `relay_url`)
// is a misconfiguration — fail fast when membership is enforced rather than
// seeding an empty-host community that no request can ever resolve to.
let deployment_community = {
let host = buzz_relay::tenant::relay_url_authority(&config.relay_url);View on GitHub (pinned to ef2aa1ae38)