micro/go-micro · error

unable to bind to %s

Error message

unable to bind to %s

What it means

Listen probes ports on a host starting from a base port, retrying up to a max number of attempts. If every bind attempt fails it gives up and returns "unable to bind to <addr>". It means no port in the scanned range could be opened on that address.

Source

Thrown at internal/util/net/net.go:78

		return nil, errors.New("unable to extract port range")
	}

	// range the ports
	for port := min; port <= max; port++ {
		// try bind to host:port
		ln, err := fn(HostPort(host, port))
		if err == nil {
			return ln, nil
		}

		// hit max port
		if port == max {
			return nil, err
		}
	}

	// why are we here?
	return nil, fmt.Errorf("unable to bind to %s", addr)
}

// Proxy returns the proxy and the address if it exits.
func Proxy(service string, address []string) (string, []string, bool) {
	var hasProxy bool

	// get proxy. we parse out address if present
	if prx := os.Getenv("MICRO_PROXY"); len(prx) > 0 {
		// default name
		if prx == "service" {
			prx = "go.micro.proxy"
			address = nil
		}

		// check if its an address
		if v := strings.Split(prx, ":"); len(v) > 1 {
			address = []string{prx}
		}

View on GitHub (pinned to 24529f1404)

Solutions

  1. Check what occupies the range: lsof -i -P | grep LISTEN or ss -ltnp, and kill stale processes.
  2. Re-run the test/program to pick a currently free port (the scan is dynamic).
  3. Widen the scan range (raise max) or start from a higher base port to avoid privileged/contended ports.
  4. Ensure previous runs are not leaking listeners (defer listener.Close()) that exhaust the range.

Example fix

// before
ln, err := net.Listen("tcp", "127.0.0.1:0")

// after
ln, err := utilnet.Listen("tcp", "127.0.0.1:0") // scans for a free port
if err != nil {
    return fmt.Errorf("no free port on localhost: %w", err)
}
defer ln.Close()
Defensive patterns

Strategy: retry

Validate before calling

func portFree(addr string) bool {
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return false
    }
    ln.Close()
    return true
}

Try / catch

ln, err := utilnet.Listen("tcp", base)
if err != nil {
    if strings.Contains(err.Error(), "unable to bind") {
        time.Sleep(500 * time.Millisecond)
        ln, err = utilnet.Listen("tcp", base) // retry once
    }
    if err != nil {
        return fmt.Errorf("port allocation failed: %w", err)
    }
}
defer ln.Close()

Prevention

When it happens

Trigger: Calling internal/util/net.Listen (used by getFreeLocalhostAddress, testPool, gRPC client setup) when all ports in the scanned range are already in use, or when the OS denies binding (permissions, address family mismatch, address in use).

Common situations: Busy CI machines where the port range is exhausted by leaked listeners from prior test runs; another process squatting on the localhost port range; running without permission to bind low ports when the range includes privileged ports.

Related errors


AI-assisted analysis of micro/go-micro@24529f1404 (2026-09-01). Data as JSON: /api/errors/c601d47df11141af. Report an issue: GitHub.