tailscale/tailscale · error

tailscaled was built without DNS %q support

Error message

tailscaled was built without DNS %q support

What it means

Linux mode detection selected 'systemd-resolved', but the resolved-backed constructor was never registered: optNewResolvedManager is only Set() by an init() in net/dns/resolved.go, which is compiled unless the build tag 'ts_omit_resolved' (or 'android') excludes it. So this binary was intentionally built without systemd-resolved support yet the environment demands it.

Source

Thrown at net/dns/manager_linux.go:109

	env.nmVersionBetween, _ = optNMVersionBetween.GetOk() // GetOk to not panic if nil; unused if optNMIsUsingResolved returns an error
	mode, err := dnsMode(logf, health, env)
	if err != nil {
		return nil, err
	}
	publishOnce.Do(func() {
		sanitizedMode := strings.ReplaceAll(mode, "-", "_")
		m := clientmetric.NewGauge(fmt.Sprintf("dns_manager_linux_mode_%s", sanitizedMode))
		m.Set(1)
	})
	logf("dns: using %q mode", mode)
	switch mode {
	case "direct":
		return newDirectManagerOnFS(logf, health, bus, env.fs), nil
	case "systemd-resolved":
		if f, ok := optNewResolvedManager.GetOk(); ok {
			return f(logf, health, interfaceName)
		}
		return nil, fmt.Errorf("tailscaled was built without DNS %q support", mode)
	case "network-manager":
		if f, ok := optNewNMManager.GetOk(); ok {
			return f(interfaceName)
		}
		return nil, fmt.Errorf("tailscaled was built without DNS %q support", mode)
	case "debian-resolvconf":
		return newDebianResolvconfManager(logf)
	case "openresolv":
		return newOpenresolvManager(logf)
	default:
		logf("[unexpected] detected unknown DNS mode %q, using direct manager as last resort", mode)
	}

	return newDirectManagerOnFS(logf, health, bus, env.fs), nil
}

// newOSConfigEnv are the funcs newOSConfigurator needs, pulled out for testing.
type newOSConfigEnv struct {

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Rebuild without ts_omit_resolved (and ts_omit_dbus) so resolved support is compiled in
  2. Or force a mode the binary supports: run tailscaled with the env/logic that selects 'direct' (e.g. replace resolv.conf with a non-resolved one or set TS_DEBUG_dns_mode if available in your tree)
  3. Verify with the startup log line 'dns: using %q mode' which mode was chosen
  4. If you ship stripped builds, document that hosts using systemd-resolved are unsupported

Example fix

# before
go build -tags ts_omit_resolved ./cmd/tailscaled
# then run on a systemd-resolved host -> error

# after
go build ./cmd/tailscaled
# or keep the stripped build but force direct mode via env: TS_DEBUG_RESOLV_CONF=... / custom resolv.conf
Defensive patterns

Strategy: validation

Validate before calling

// before selecting a mode, check the hook your build provides:
if _, ok := optNewResolvedManager.GetOk(); !ok {
	return errors.New("this build lacks resolved support; rebuild without ts_omit_resolved or force direct mode")
}

Type guard

func supportsResolved() bool { _, ok := optNewResolvedManager.GetOk(); return ok }

Prevention

When it happens

Trigger: Building tailscaled with -tags ts_omit_resolved (or a vendored build like synology/qnap images that strip dbus deps) and then running on a host where /etc/resolv.conf points at 127.0.0.53 and mode detection returns 'systemd-resolved'.

Common situations: Custom minimal builds for appliances, tailscaled embedded in third-party products, cross-compiled images reused on regular distros, CI builds with restrictive tags.

Related errors


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/4a65f717ba127fb0. Report an issue: GitHub.