netbirdio/netbird · error
device is not ready yet
Error message
device is not ready yet
What it means
Returned by TunDevice.Up() (userspace unix) when t.device is nil. t.device is only set inside Create() after the tun device is opened, so this error indicates Up() was called before a successful Create(), or a Create() error was ignored. Same lifecycle contract as the netstack variant.
Source
Thrown at client/iface/device/device_usp_unix.go:80
err = t.assignAddr()
if err != nil {
t.device.Close()
return nil, fmt.Errorf("error assigning ip: %s", err)
}
t.configurer = configurer.NewUSPConfigurer(t.device, t.name, t.iceBind.ActivityRecorder())
err = t.configurer.ConfigureInterface(t.key, t.port)
if err != nil {
t.device.Close()
t.configurer.Close()
return nil, fmt.Errorf("error configuring interface: %s", err)
}
return t.configurer, nil
}
func (t *TunDevice) Up() (*udpmux.UniversalUDPMuxDefault, error) {
if t.device == nil {
return nil, fmt.Errorf("device is not ready yet")
}
err := t.device.Up()
if err != nil {
return nil, err
}
udpMux, err := t.iceBind.GetICEMux()
if err != nil {
return nil, err
}
t.udpMux = udpMux
log.Debugf("device is ready to use: %s", t.name)
return udpMux, nil
}
func (t *TunDevice) UpdateAddr(address wgaddr.Address) error {View on GitHub (pinned to 93e97f4bf1)
Solutions
- Guarantee Create() succeeded (error checked) before calling Up()
- On reconnect paths, treat this error as a signal to re-run Create() first
- Protect device lifecycle transitions with a mutex or single state owner
Example fix
// before
mux, err := dev.Up()
// after
if _, cerr := dev.Create(); cerr != nil {
return nil, cerr
}
mux, err := dev.Up() Defensive patterns
Strategy: validation
Validate before calling
// Up() requires a device created by a successful Create()
if dev.Device() == nil {
if _, err := dev.Create(); err != nil {
return nil, err
}
} Type guard
func deviceReady(dev *device.TunDevice) bool {
return dev != nil && dev.Device() != nil
} Try / catch
mux, err := dev.Up()
if err != nil && strings.Contains(err.Error(), "device is not ready yet") {
// ordering bug: create first, then retry Up()
} Prevention
- Check Create()'s error before every follow-up call
- Drive reconnect flows from the top of the lifecycle (Create, then Up)
- Serialize lifecycle transitions across goroutines
When it happens
Trigger: Calling Up() before Create(); ignoring the error from Create() and continuing; Close() and Up() racing from different goroutines.
Common situations: Reconnect logic that retries Up() without re-running Create(); concurrent engine stop/start; tests calling Up() directly.
Related errors
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/cb69100e96956874.
Report an issue: GitHub.