slackhq/nebula · error

already listening on port %d

Error message

already listening on port %d

What it means

Each Service keeps a registry of active listeners keyed by port under its mutex. Listen rejects a bind attempt when a listener already occupies the requested port, mirroring an EADDRINUSE from a real kernel. The port must be unique for the lifetime of the Service.

Source

Thrown at service/service.go:240

		return nil, errors.New("specific port required, got 0")
	}
	if addr.Port < 0 || addr.Port >= math.MaxUint16 {
		return nil, fmt.Errorf("invalid port %d", addr.Port)
	}
	port := uint16(addr.Port)

	l := &tcpListener{
		port:   port,
		s:      s,
		addr:   addr,
		accept: make(chan net.Conn),
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	if _, ok := s.mu.listeners[port]; ok {
		return nil, fmt.Errorf("already listening on port %d", port)
	}
	s.mu.listeners[port] = l

	return l, nil
}

func (s *Service) Wait() error {
	return s.eg.Wait()
}

func (s *Service) Close() error {
	s.control.Stop()
	return nil
}

func (s *Service) tcpHandler(r *tcp.ForwarderRequest) {
	endpointID := r.ID()

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Close the existing listener (l.Close()) before listening on the same port again.
  2. Use a different port for the new listener.
  3. Create a new Service instance if you need an isolated port table.

Example fix

// before
l2, err := svc.Listen("tcp", ":8080") // already listening
// after
l1.Close()
l2, err := svc.Listen("tcp", ":8080")
Defensive patterns

Strategy: try-catch

Validate before calling

if activeListeners[port] {
    return fmt.Errorf("port %d already bound by our own listener", port)
}

Try / catch

l, err := svc.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
    if strings.Contains(err.Error(), "already listening on port") {
        // treat as EADDRINUSE: close stale listener or pick another port
        return ErrPortInUse
    }
    return err
}

Prevention

When it happens

Trigger: Calling Service.Listen twice with the same wildcard port, e.g. Listen("tcp", ":8080") while a previous tcpListener on 8080 is still registered (service/service.go:240).

Common situations: Restarting a test server without closing the old listener; two test helpers both binding the same fixed port; forgetting to call Close() on the previous listener before rebinding; parallel tests sharing one Service.

Related errors


AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03). Data as JSON: /api/errors/519a6d441e82d994. Report an issue: GitHub.