juanfont/headscale · error

%s sending confirm request: %w

Error message

%s sending confirm request: %w

What it means

`hc.Do(req)` failed while POSTing the confirm form — a transport-level error (connection refused/reset, DNS failure, timeout), not an HTTP status. The wrapper annotates it with the client hostname so you can tell which tailscale node's registration failed.

Source

Thrown at integration/scenario.go:1321

	log.Printf("%s auto-submitting confirm form: %s", hostname, confirmURL)

	formData := url.Values{
		"headscale_register_confirm": {csrfToken},
	}

	ctx := context.Background()

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, confirmURL.String(), strings.NewReader(formData.Encode()))
	if err != nil {
		return "", nil, fmt.Errorf("%s creating confirm request: %w", hostname, err)
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	confirmResp, err := hc.Do(req)
	if err != nil {
		return "", nil, fmt.Errorf("%s sending confirm request: %w", hostname, err)
	}
	defer confirmResp.Body.Close()

	confirmBytes, err := io.ReadAll(confirmResp.Body)
	if err != nil {
		return "", nil, fmt.Errorf("%s reading confirm response: %w", hostname, err)
	}

	if confirmResp.StatusCode != http.StatusOK {
		return string(confirmBytes), nil, fmt.Errorf( //nolint:err113
			"%s confirm returned status %d: %s",
			hostname, confirmResp.StatusCode, string(confirmBytes),
		)
	}

	return string(confirmBytes), nil, nil
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Check the wrapped error: 'connection refused' means the server is down — inspect the headscale container logs.
  2. Verify the confirm URL host matches the address the scenario actually reaches headscale on.
  3. Retry once — integration tests over Docker bridges throw transient resets.
  4. Increase the client timeout if the wrapped error is a deadline exceeded.
Defensive patterns

Strategy: retry

Try / catch

// Retry transport failures once; surface HTTP-status failures immediately.
resp, err := hc.Do(req)
if err != nil {
    time.Sleep(500 * time.Millisecond)
    resp, err = hc.Do(req)
    if err != nil {
        return "", nil, fmt.Errorf("%s sending confirm request: %w", hostname, err)
    }
}

Prevention

When it happens

Trigger: The headscale container is not listening on the confirm URL's host:port, the Docker network is down, TLS handshake fails (the client skips verify but the server may still break), or the context deadline expires during the POST.

Common situations: Headscale container crashed between the GET and the POST; the URL points at a host name resolvable only inside a Docker network the client is not attached to; test environment DNS flake.

Related errors


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