tailscale/tailscale · error

failed to construct request: %s

Error message

failed to construct request: %s

What it means

The kube metrics server proxies incoming requests to tailscaled's LocalAPI usermetrics endpoint by first rebuilding the request with http.NewRequestWithContext(r.Context(), r.Method, url, r.Body). This constructor validates the method token and URL, so a malformed method (illegal characters, empty) or invalid target URL yields this 500 'failed to construct request'. It is a client-protocol error surfaced by the proxy, not a metrics failure.

Source

Thrown at kube/metrics/metrics.go:29

	"fmt"
	"io"
	"net/http"

	"tailscale.com/client/local"
	"tailscale.com/client/tailscale/apitype"
)

// metrics is a simple metrics HTTP server, if enabled it forwards requests to
// the tailscaled's LocalAPI usermetrics endpoint at /localapi/v0/usermetrics.
type metrics struct {
	debugEndpoint string
	lc            *local.Client
}

func proxy(w http.ResponseWriter, r *http.Request, url string, do func(*http.Request) (*http.Response, error)) {
	req, err := http.NewRequestWithContext(r.Context(), r.Method, url, r.Body)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to construct request: %s", err), http.StatusInternalServerError)
		return
	}
	req.Header = r.Header.Clone()

	resp, err := do(req)
	if err != nil {
		http.Error(w, fmt.Sprintf("failed to proxy request: %s", err), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	for key, val := range resp.Header {
		for _, v := range val {
			w.Header().Add(key, v)
		}
	}
	w.WriteHeader(resp.StatusCode)
	if _, err := io.Copy(w, resp.Body); err != nil {

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Send a normal, valid HTTP method (GET/POST) to the metrics endpoints
  2. If embedding this package, validate the configured debugEndpoint is a valid host:port before serving
  3. Reproduce with curl to confirm the raw request line is well-formed
Defensive patterns

Strategy: validation

Validate before calling

// Embedders: validate config before serving
if _, err := url.Parse("http://" + debugEndpoint + "/"); debugEndpoint != "" && err != nil {
    return fmt.Errorf("invalid debug endpoint %q: %w", debugEndpoint, err)
}

Try / catch

if resp.StatusCode == http.StatusInternalServerError && strings.Contains(body, "failed to construct request") {
    // malformed method/URL — fix the client's request line; retrying the same bytes will fail identically
}

Prevention

When it happens

Trigger: A client issuing an HTTP request with an invalid method token (non-token bytes) that still reached the handler (e.g. via a frontend that relaxes validation), or an unusable target URL — in practice nearly impossible with the fixed http://LocalAPIHost/... URL unless the library is embedded and url/debugEndpoint are overridden with a malformed value.

Common situations: Embedding kube/metrics with a custom debug endpoint string that is not a valid absolute URL (missing scheme/host, control characters); exotic HTTP clients sending custom verbs with spaces.

Related errors


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