slackhq/nebula · critical

unable to bind to socket: %w

Error message

unable to bind to socket: %w

What it means

NewListener wraps the errno from unix.Bind when the kernel refuses to bind the UDP socket to the configured listen address/port. The fd is closed and listener creation fails. The wrapped err is the raw bind errno (EADDRINUSE, EACCES, EADDRNOTAVAIL).

Source

Thrown at udp/udp_linux.go:71

			_ = unix.Close(fd)
			return nil, fmt.Errorf("unable to set SO_REUSEPORT: %w", err)
		}
	}

	var sa unix.Sockaddr
	port := int(s.Listen.Port())
	if s.Listen.Addr().Is4() {
		sa4 := &unix.SockaddrInet4{Port: port}
		sa4.Addr = s.Listen.Addr().As4()
		sa = sa4
	} else {
		sa6 := &unix.SockaddrInet6{Port: port}
		sa6.Addr = s.Listen.Addr().As16()
		sa = sa6
	}
	if err = unix.Bind(fd, sa); err != nil {
		_ = unix.Close(fd)
		return nil, fmt.Errorf("unable to bind to socket: %w", err)
	}

	out := &StdConn{sysFd: fd, isV4: s.Listen.Addr().Is4(), l: l, batch: s.Batch}

	out.bw = newBatchWriter(fd, out.isV4, l, s.Offloads)

	// GRO coalesces same-flow datagrams into superpackets that must be split back apart via the delivered gso_size cmsg
	// batch == 1 means the caller wants plain single-datagram reads with MTU-sized buffers, so leave it off.
	if s.Batch > 1 && s.Offloads {
		out.prepareGRO()
	}

	return out, nil
}

// udpGROBufferSize sizes the per-entry recvmmsg buffer when UDP_GRO is on.
// The kernel stitches a run of same-flow datagrams into a single skb whose
// length is bounded by sk_gso_max_size (65535)

View on GitHub (pinned to dd8f660c0a)

Solutions

  1. Free the port: stop the conflicting process (ss -ulnp | grep <port>) or change listeners.port in config
  2. Verify listeners.host is an address actually assigned to the machine (or use 0.0.0.0)
  3. Run with CAP_NET_BIND_SERVICE or a non-privileged port if binding <1024
  4. Check the wrapped errno in the error chain to distinguish EADDRINUSE vs EACCES vs EADDRNOTAVAIL

Example fix

// before (nebula.yaml, non-root)
listeners:
  host: 0.0.0.0
  port: 443
// after
listeners:
  host: 0.0.0.0
  port: 4242
Defensive patterns

Strategy: validation

Validate before calling

// before starting: verify the port is free and the address exists
func portFree(host string, port int) error {
    conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP(host), Port: port})
    if err != nil { return err }
    conn.Close()
    return nil
}

Try / catch

l, err := udp.NewListener(...)
if err != nil {
    var errno syscall.Errno
    if errors.As(err, &errno) {
        switch errno {
        case syscall.EADDRINUSE: // stop conflicting process or change port
        case syscall.EACCES:    // need root/CAP_NET_BIND_SERVICE
        case syscall.EADDRNOTAVAIL: // fix listeners.host
        }
    }
    return err
}

Prevention

When it happens

Trigger: Calling udp.NewListener with listeners.host/port that is already bound by another process without SO_REUSEPORT, an address not assigned to the host, or a privileged port (<1024) without CAP_NET_BIND_SERVICE/root.

Common situations: Another instance of nebula (or any UDP service) already holds the port; listen host typo (address not on this machine); binding port 443/UDP as non-root in a container without capabilities; stale nebula process after a crash.

Related errors


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