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
- Check the wrapped error: 'connection refused' means the server is down — inspect the headscale container logs.
- Verify the confirm URL host matches the address the scenario actually reaches headscale on.
- Retry once — integration tests over Docker bridges throw transient resets.
- 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
- Confirm headscale is reachable (health endpoint) before starting registration flows.
- Set an explicit, generous Timeout on the http.Client instead of the default none.
- Keep the confirm URL host identical to the one the initial GET succeeded on.
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
- test pattern is required as first argument or use --test fla
- directory is required
- %s reading confirm response: %w
- health check timed out
- unexpected end of container wait
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/9b4cb9d8163169c9.
Report an issue: GitHub.