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, nil

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Call Create() first and handle its error before ever calling Up()
  2. Serialize Create/Up/Close behind a mutex or state machine so teardown cannot interleave with bring-up
  3. 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

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.