FuelLabs/fuel-core · error

Failed to bind to address {}: {}

Error message

Failed to bind to address {}: {}

What it means

While starting the block aggregator API service, TcpIncoming::bind(addr) — the hyper/tonic listener used by the gRPC server — failed; the message includes the address and the OS error. Almost always the port is already in use, or the process lacks privileges to bind a port below 1024.

Source

Thrown at crates/services/block_aggregator_api/src/api/protobuf_adapter.rs:284

    }
}

pub type APIService = ServiceRunner<UninitializedTask>;

pub fn incoming_and_server(
    addr: SocketAddr,
) -> anyhow::Result<(TcpIncoming, tonic::transport::Server)> {
    let tcp_nodelay = true;
    let tcp_keepalive = None;
    let accept_http1 = false;

    let tonic_server = tonic::transport::Server::default()
        .tcp_nodelay(tcp_nodelay)
        .tcp_keepalive(tcp_keepalive)
        .accept_http1(accept_http1);

    let incoming = TcpIncoming::bind(addr)
        .map_err(|e| anyhow::anyhow!("Failed to bind to address {}: {}", addr, e))?
        .with_nodelay(Some(tcp_nodelay))
        .with_keepalive(tcp_keepalive);

    Ok((incoming, tonic_server))
}

pub fn new_service_with_custom_incoming<B>(
    mut tonic_server: tonic::transport::Server,
    incoming: TcpIncoming,
    block_aggregator: B,
) -> anyhow::Result<APIService>
where
    B: BlocksAggregatorApi,
{
    let server = Server::new(block_aggregator);

    let router = tonic_server.add_service(ProtoBlockAggregatorServer::new(server));

View on GitHub (pinned to b9d4d170da)

Solutions

  1. Identify the occupant with ss -ltnp (or lsof -i :<port>) and stop or reap that process.
  2. Change the configured bind address or port, or bind port 0 to let the OS assign a free port.
  3. For ports below 1024, grant the capability (setcap cap_net_bind_service) or switch to a high port.
  4. If a stale socket lingers in TIME_WAIT, wait it out or restart with address reuse enabled.

Example fix

// before
let addr: SocketAddr = "0.0.0.0:4280".parse()?;
let (incoming, server) = new_service_and_incoming(block_aggregator, addr).await?;

// after: avoid the conflict
let addr: SocketAddr = "0.0.0.0:4281".parse()?; // or pick a free port programmatically
Defensive patterns

Strategy: validation

Validate before calling

use std::net::TcpListener;

fn addr_bindable(addr: &std::net::SocketAddr) -> bool {
    TcpListener::bind(addr).is_ok()
}

// before starting the block aggregator API
if !addr_bindable(&config.addr) {
    return Err(anyhow::anyhow!("address {} is not bindable (in use or privileged)", config.addr));
}

Try / catch

match new_service_and_incoming(aggregator, addr).await {
    Err(e) if e.to_string().contains("Failed to bind") => {
        // free the port (ss -ltnp | grep <port>), pick another port, or use port 0; then retry
    }
    rest => rest,
}

Prevention

When it happens

Trigger: Starting the block aggregator API with a port held by another process (second node instance, leftover process, lingering socket), binding a privileged port as non-root, or an otherwise invalid or unavailable address.

Common situations: Running two aggregator instances with the same config; a crashed process leaving the socket occupied; container port mappings colliding; firewall or SELinux denying the bind.

Related errors


AI-assisted analysis of FuelLabs/fuel-core@b9d4d170da (2026-08-16). Data as JSON: /api/errors/f6fba5f32b84583c. Report an issue: GitHub.