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
- Free the port: stop the conflicting process (ss -ulnp | grep <port>) or change listeners.port in config
- Verify listeners.host is an address actually assigned to the machine (or use 0.0.0.0)
- Run with CAP_NET_BIND_SERVICE or a non-privileged port if binding <1024
- 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
- Validate listeners.host is a local interface address before launch
- Pick non-privileged ports (>1024) for non-root deployments
- Use systemd socket activation or grant CAP_NET_BIND_SERVICE for low ports
- Check ss -ulnp / netstat for port conflicts in deployment automation
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
- bind: %w
- unable to open socket: %w
- unable to set SO_REUSEPORT: %w
- unsupported sock type: %T
- WriteBatch: len(bufs)=%d != len(addrs)=%d
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/96ca915e33829445.
Report an issue: GitHub.