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
- Normalize and validate ServerURL once at startup with url.Parse: require a non-empty Host and scheme http/https
- Trim whitespace and strip line endings from config-supplied URLs
- 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
- Normalize every externally sourced URL with strings.TrimSpace and url.Parse before use
- Fail config validation at process start rather than at first RPC
- Add config schema tests that reject scheme-less or whitespace-bearing URLs
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
- invalid ServerURL scheme, must be http or https
- derphttp.NewClient: %v
- derphttp.NewClient: invalid URL scheme %q
- no nodes for %s
- route %s has non-address bits set; expected %s
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/7c7e59a0df87ea47.
Report an issue: GitHub.