tailscale/tailscale · error

creating map request: %w

Error message

creating map request: %w

What it means

Returned by Client.SendMapUpdate when http.NewRequestWithContext rejects the URL constructed as c.serverURL + "/machine/map". NewRequestWithContext fails only when url.Parse fails: control characters in the URL, whitespace/newlines, a missing or malformed scheme/host, or an unusable method. This is purely local URL validation; nothing has been sent.

Source

Thrown at control/tsp/map.go:309

		Stream:    false,
		ReadOnly:  false,
	}

	body, err := json.Marshal(mapReq)
	if err != nil {
		return fmt.Errorf("encoding map request: %w", err)
	}

	nc, err := c.noiseClient(ctx)
	if err != nil {
		return fmt.Errorf("establishing noise connection: %w", err)
	}

	url := c.serverURL + "/machine/map"
	url = strings.Replace(url, "http:", "https:", 1)
	req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("creating map request: %w", err)
	}
	ts2021.AddLBHeader(req, opts.NodeKey.Public())

	res, err := nc.Do(req)
	if err != nil {
		return fmt.Errorf("map request: %w", err)
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		msg, _ := io.ReadAll(res.Body)
		return fmt.Errorf("map request: http %d: %.200s",
			res.StatusCode, strings.TrimSpace(string(msg)))
	}
	io.Copy(io.Discard, res.Body)
	return nil
}

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Normalize and validate ServerURL once at startup with url.Parse: require a non-empty Host and scheme http/https
  2. Trim whitespace and strip line endings from config-supplied URLs
  3. Log the exact composed URL (c is internal, so mirror it from your ClientOpts.ServerURL + "/machine/map") when debugging

Example fix

// before
serverURL := os.Getenv("TS_SERVER_URL") // "https://control.example.com\n"
c, _ := tsp.NewClient(tsp.ClientOpts{MachineKey: mk, ServerURL: serverURL})

// after
serverURL := strings.TrimSpace(os.Getenv("TS_SERVER_URL"))
u, err := url.Parse(serverURL)
if err != nil || u.Host == "" || (u.Scheme != "http" && u.Scheme != "https") {
    return fmt.Errorf("bad TS_SERVER_URL %q", serverURL)
}
c, _ := tsp.NewClient(tsp.ClientOpts{MachineKey: mk, ServerURL: serverURL})
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(serverURL)
if err != nil || u.Host == "" || (u.Scheme != "http" && u.Scheme != "https") {
    return fmt.Errorf("invalid control server URL %q", serverURL)
}
c, err := tsp.NewClient(tsp.ClientOpts{MachineKey: mk, ServerURL: serverURL})

Type guard

func validControlURL(raw string) bool {
    u, err := url.Parse(strings.TrimSpace(raw))
    return err == nil && u.Host != "" && (u.Scheme == "http" || u.Scheme == "https")
}

Try / catch

if err := c.SendMapUpdate(ctx, opts); err != nil {
    if strings.HasPrefix(err.Error(), "creating map request") {
        // deterministic config bug: the URL is malformed; fail fast, do not retry
        return fmt.Errorf("check ServerURL config: %w", err)
    }
}

Prevention

When it happens

Trigger: ServerURL loaded from config without a scheme (e.g. "controlplane.tailscale.com"), containing a trailing newline or space from a file/env var, or containing a control character. The strings.Replace(http:→https:) step does not repair a malformed base URL.

Common situations: Reading ServerURL from an env var or .env file with a trailing CR/LF; YAML config parsed with a stray quote; URL pasted with an embedded space; empty ServerURL plus a modified DefaultServerURL.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


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