juanfont/headscale · warning

resolving destination: %w

Error message

resolving destination: %w

What it means

The destination alias in a policy test parsed but failed to resolve against the policy, users, and nodes. The wrapped error names the real cause — typically ErrHostResolve (unknown host) or a user/autogroup problem — occurring on the destination side of an accept/deny check.

Source

Thrown at hscontrol/policy/v2/test.go:354

	return addrs.Prefixes(), nil
}

// evalReachability reports whether traffic from any srcPrefix to dst (in
// `host:port` form) is allowed by filter for the requested protocol.
//
// Empty proto means the default set the client applies when proto is
// omitted (TCP/UDP/ICMP) — we accept a rule whose IPProto list contains
// any of those, or rules with no IPProto restriction at all.
func evalReachability(srcPrefixes []netip.Prefix, dst string, proto Protocol, pol *Policy, filter []tailcfg.FilterRule, users []types.User, nodes views.Slice[types.NodeView]) (bool, error) {
	awp, err := parseDestinationAlias(dst)
	if err != nil {
		return false, fmt.Errorf("invalid destination %q: %w", dst, err)
	}

	dstAddrs, err := awp.Resolve(pol, users, nodes)
	if err != nil {
		return false, fmt.Errorf("resolving destination: %w", err)
	}

	if dstAddrs == nil || dstAddrs.Empty() {
		return false, fmt.Errorf("%w: %q", errTestDestinationNoIP, dst)
	}

	dstPrefixes := dstAddrs.Prefixes()

	// Tailscale's tests semantics: ALL src prefixes must reach the dst for
	// the test to consider it allowed. A partial allow is a fail.
	for _, src := range srcPrefixes {
		if !srcReachesDst(src, dstPrefixes, awp.Ports, proto, filter) {
			return false, nil
		}
	}

	return true, nil
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Read the wrapped error after 'resolving destination:' to get the exact cause.
  2. If a hostname, add it to the 'hosts' section of the policy (e.g. "web": "100.64.0.10/32") or use the IP/CIDR directly.
  3. If a user/tag destination, confirm the user exists or the tag is used somewhere in the policy.
  4. Avoid autogroups as destinations; they are source-oriented.

Example fix

// before
"hosts": {},
"tests": [{"src": "a@example.com", "accept": ["web:80"]}]

// after
"hosts": {"web": "100.64.0.10/32"},
"tests": [{"src": "a@example.com", "accept": ["web:80"]}]
Defensive patterns

Strategy: validation

Validate before calling

// Ensure every hostname used in tests exists in the hosts map.
func hostDeclared(h string, hosts map[v2.Host]netip.Prefix) bool {
    _, ok := hosts[v2.Host(h)]
    return ok
}

Try / catch

if err := runPolicyTests(...); err != nil {
    if errors.Is(err, v2.ErrHostResolve) {
        // destination host missing from hosts map: add it or switch to CIDR
    }
    return err
}

Prevention

When it happens

Trigger: A test destination like 'unknownhost:80' where 'unknownhost' is not defined in the policy's hosts map, or an autogroup/tag destination the engine cannot resolve. awp.Resolve(pol, users, nodes) errors inside evalReachability.

Common situations: Destination hostnames drift out of sync with the hosts section after renames; tests written against Tailscale semantics using destination aliases headscale cannot resolve (e.g. autogroup:self as a destination).

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/86d1d01fbaf9cc6e. Report an issue: GitHub.