block/buzz · error

Failed to bind {}: {e}

Error message

Failed to bind {}: {e}

What it means

serve() binds the main WebSocket/HTTP listener on config.bind_addr, which comes from BUZZ_BIND_ADDR (default 0.0.0.0:3000, config.rs:462-464); this error wraps the tokio TcpListener::bind io::Error. Address syntax problems are rejected earlier at config load (ConfigError 'invalid BUZZ_BIND_ADDR'), so reaching this line means the address parsed fine but the OS refused the bind — usually AddrInUse, AddrNotAvailable (IP not present on any interface), or PermissionDenied on privileged ports.

Source

Thrown at crates/buzz-relay/src/main.rs:1332

        // retains ownership of every delayed close until its 1012 frame has
        // been flushed and acknowledged (or its send loop cancelled).
        let closed = if drain_jitter_ms == 0 {
            drain_conn_manager.drain_all()
        } else {
            drain_conn_manager.drain_all_jittered(drain_jitter_ms).await
        };
        info!(
            connections = closed,
            jitter_ms = drain_jitter_ms,
            max_jitter_ms = MAX_DRAIN_JITTER_MS,
            "Signalled restart close to all live WebSocket connections"
        );
        hard_shutdown_abort
    });

    let tcp_listener = tokio::net::TcpListener::bind(&config.bind_addr)
        .await
        .map_err(|e| anyhow::anyhow!("Failed to bind {}: {e}", config.bind_addr))?;
    info!(addr = %config.bind_addr, "buzz-relay TCP listening");

    #[cfg(unix)]
    if let Some(ref uds_path) = config.uds_path {
        use std::os::unix::fs::FileTypeExt as _;
        match std::fs::symlink_metadata(uds_path) {
            Ok(meta) if meta.file_type().is_socket() => {
                let _ = std::fs::remove_file(uds_path);
            }
            Ok(_) => {
                return Err(anyhow::anyhow!(
                    "BUZZ_UDS_PATH {uds_path} exists but is not a socket"
                ));
            }
            Err(_) => {}
        }
        let uds_listener = tokio::net::UnixListener::bind(uds_path)
            .map_err(|e| anyhow::anyhow!("Failed to bind UDS {uds_path}: {e}"))?;

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Check the holder: ss -ltnp 'sport = :3000' and stop it, or change BUZZ_BIND_ADDR to a free port.
  2. If binding a specific IP, verify it exists on an interface (ip addr); otherwise bind 0.0.0.0 or fix the address.
  3. Privileged ports require CAP_NET_BIND_SERVICE; prefer a port > 1024.
  4. Give each co-located relay instance its own BUZZ_BIND_ADDR (and BUZZ_HEALTH_PORT).

Example fix

# before: Error: Failed to bind 0.0.0.0:3000: Address already in use (os error 98)
BUZZ_BIND_ADDR=0.0.0.0:3000

# after
BUZZ_BIND_ADDR=0.0.0.0:3001
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: confirm the main bind address is bindable before boot.
fn bind_addr_available(addr: std::net::SocketAddr) -> bool {
    std::net::TcpListener::bind(addr).is_ok()
}

let addr: std::net::SocketAddr = std::env::var("BUZZ_BIND_ADDR")
    .unwrap_or_else(|_| "0.0.0.0:3000".into())
    .parse()
    .expect("BUZZ_BIND_ADDR must parse");
assert!(bind_addr_available(addr), "{addr} not bindable — in use, privileged, or not on an interface");

Type guard

fn classify_bind_error(e: &std::io::Error) -> &'static str {
    match e.kind() {
        std::io::ErrorKind::AddrInUse => "port in use",
        std::io::ErrorKind::AddrNotAvailable => "IP not on any interface",
        std::io::ErrorKind::PermissionDenied => "privileged port without CAP_NET_BIND_SERVICE",
        _ => "other",
    }
}

Try / catch

let tcp_listener = match tokio::net::TcpListener::bind(&config.bind_addr).await {
    Ok(l) => l,
    Err(e) if e.kind() == std::io::ErrorKind::AddrInUse => {
        return Err(anyhow!("{} in use — change BUZZ_BIND_ADDR or stop the holder", config.bind_addr))
    }
    Err(e) => return Err(anyhow!("Failed to bind {}: {e}", config.bind_addr)),
};

Prevention

When it happens

Trigger: Another process on :3000 (web/dev tooling, a second relay); BUZZ_BIND_ADDR naming a cluster-internal or stale static IP that does not exist on any local interface (EADDRNOTAVAIL); binding a port < 1024 without capabilities; host-networked replicas with identical BUZZ_BIND_ADDR.

Common situations: Local dev with node/vite/another relay already on 3000; k8s manifests setting BUZZ_BIND_ADDR to a Service IP instead of a pod-local address; migrating to a node where the previously bound static IP no longer exists.

Related errors


AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16). Data as JSON: /api/errors/2e3633783dc422ce. Report an issue: GitHub.