juanfont/headscale · error · HTTPError
Internal error
Error message
Internal error
What it means
Returned by sshActionHoldAndDelegate when url.Parse fails on the internally constructed hold URL (ServerURL + /machine/ssh/action/... with $ placeholders). Because the path is a fixed constant, failure can only come from a malformed ServerURL configuration that makes the combined URL unparseable.
Source
Thrown at hscontrol/noise.go:531
// sshActionHoldAndDelegate creates a new auth session bound to the
// (src, dst) pair and returns a [tailcfg.SSHAction.HoldAndDelegate] action that directs the
// client to authenticate.
func (ns *noiseServer) sshActionHoldAndDelegate(
reqLog zerolog.Logger,
action *tailcfg.SSHAction,
srcNodeID, dstNodeID types.NodeID,
) (*tailcfg.SSHAction, error) {
holdURL, err := url.Parse(
ns.headscale.cfg.ServerURL +
"/machine/ssh/action/$SRC_NODE_ID/to/$DST_NODE_ID" +
"?local_user=$LOCAL_USER",
)
if err != nil {
return nil, NewHTTPError(
http.StatusInternalServerError,
"Internal error",
fmt.Errorf("parsing SSH action URL: %w", err),
)
}
authID, err := types.NewAuthID()
if err != nil {
return nil, NewHTTPError(
http.StatusInternalServerError,
"Internal error",
fmt.Errorf("generating auth ID: %w", err),
)
}
ns.headscale.state.SetAuthCacheEntry(
authID,
types.NewSSHCheckAuthRequest(srcNodeID, dstNodeID),
)
authURL := ns.headscale.authProvider.AuthURL(authID)View on GitHub (pinned to 565fd254d0)
Solutions
- Print and inspect the configured server_url for stray characters
- Set server_url to a clean absolute URL like https://headscale.example.com
- Restart headscale after fixing the config
Example fix
# before server_url: https://headscale.example.com\x0b # after server_url: https://headscale.example.com
Defensive patterns
Strategy: validation
Validate before calling
if u, err := url.Parse(cfg.ServerURL); err != nil || !u.IsAbs() {
return errors.New("server_url must be a valid absolute URL")
} Prevention
- Validate server_url at startup before serving
- Keep config values free of control characters — generate configs, do not paste through terminals that can inject them
When it happens
Trigger: cfg.ServerURL containing characters that break URL parsing when concatenated (control characters, invalid percent-encoding).
Common situations: HEADCSCALE_SERVER_URL pasted with hidden control characters or an invalid scheme; a config typo introduced during deployment.
Related errors
- wildcard (*) is not supported as SSH destination
- is above the max (168h)
- MOCKOIDC_CLIENT_ID not defined
- auth request rejected
- STUN address not set
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/10061c91c13df197.
Report an issue: GitHub.