netbirdio/netbird · error
device is not ready yet
Error message
device is not ready yet
What it means
Returned by TunNetstackDevice.Up() when t.device is still nil. t.device is only assigned inside create(), so this error means Up() ran before a successful create(), or the create() error was swallowed and the caller continued anyway. It is a lifecycle-ordering error, not an environment failure.
Source
Thrown at client/iface/device/device_netstack.go:102
device.NewLogger(wgLogLevel(), "[netbird] "),
)
t.configurer = configurer.NewUSPConfigurerNoUAPI(t.device, t.name, t.bind.ActivityRecorder())
err = t.configurer.ConfigureInterface(t.key, t.port)
if err != nil {
if cErr := tunIface.Close(); cErr != nil {
log.Debugf("failed to close tun device: %v", cErr)
}
return nil, fmt.Errorf("error configuring interface: %s", err)
}
log.Debugf("device has been created: %s", t.name)
return t.configurer, nil
}
func (t *TunNetstackDevice) 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.bind.GetICEMux()
if err != nil && !errors.Is(err, bind.ErrUDPMUXNotSupported) {
return nil, err
}
if udpMux != nil {
t.udpMux = udpMux
}
log.Debugf("netstack device is ready to use")
return udpMux, nilView on GitHub (pinned to 93e97f4bf1)
Solutions
- Call Create() first and handle its error before ever calling Up()
- Serialize Create/Up/Close behind a mutex or state machine so teardown cannot interleave with bring-up
- On this error, re-run Create() and then retry Up()
Example fix
// before
mux, err := dev.Up()
// after
if dev.Device() == nil {
if _, cerr := dev.Create(); cerr != nil {
return nil, cerr
}
}
mux, err := dev.Up() Defensive patterns
Strategy: validation
Validate before calling
// Up() requires a successfully created device; check readiness first
if dev.Device() == nil {
if _, err := dev.Create(); err != nil {
return nil, err
}
} Type guard
func netstackReady(dev *device.TunNetstackDevice) 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") {
// re-create then retry Up once
} Prevention
- Treat Create/Up/Close as one state machine owned by a single goroutine
- Always check the error returned by Create() before any subsequent call
- In reconnect loops, restart from Create(), not from Up()
When it happens
Trigger: Calling Up() before Create(); discarding the error returned by Create() and proceeding; a reconnect/retry path that skips the create step; Close() nil racing a new Up().
Common situations: Custom embedding of the agent that drives Up/Close directly, goroutine races between teardown and reconnect, tests invoking Up() on a fresh device.
Related errors
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/f046c33da14779e6.
Report an issue: GitHub.