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
- Rebuild without ts_omit_resolved (and ts_omit_dbus) so resolved support is compiled in
- 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)
- Verify with the startup log line 'dns: using %q mode' which mode was chosen
- 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
- Match build tags to deployment targets (no ts_omit_resolved on systemd distros)
- Smoke-test built binaries for the modes they will encounter
- Document stripped-build limitations to operators
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
- failed refresh apk repository indexes: %w, output: %s
- NewDirWatcher: %w
- reading /etc/resolv.conf: %w
- {res.Error}
- invalid dns.OSConfig: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/4a65f717ba127fb0.
Report an issue: GitHub.