From 3bd4ecd9cd8ae0e349e3e3d728a9e066642931c1 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 11 Nov 2025 17:42:07 +0100 Subject: [PATCH] fix: preserve node expiry when tailscaled restarts When tailscaled restarts, it sends RegisterRequest with Auth=nil and Expiry=zero. Previously this was treated as a logout because time.Time{}.Before(time.Now()) returns true. Add early return in handleRegister() to detect this case and preserve the existing node state without modification. Fixes #2862 --- hscontrol/auth.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hscontrol/auth.go b/hscontrol/auth.go index e4a0d089..447035da 100644 --- a/hscontrol/auth.go +++ b/hscontrol/auth.go @@ -71,6 +71,13 @@ func (h *Headscale) handleRegister( // We do not look up nodes by [key.MachinePublic] as it might belong to multiple // nodes, separated by users and this path is handling expiring/logout paths. if node, ok := h.state.GetNodeByNodeKey(req.NodeKey); ok { + // When tailscaled restarts, it sends RegisterRequest with Auth=nil and Expiry=zero. + // Return the current node state without modification. + // See: https://github.com/juanfont/headscale/issues/2862 + if req.Expiry.IsZero() && node.Expiry().Valid() && !node.IsExpired() { + return nodeToRegisterResponse(node), nil + } + resp, err := h.handleLogout(node, req, machineKey) if err != nil { return nil, fmt.Errorf("handling existing node: %w", err) @@ -173,6 +180,7 @@ func (h *Headscale) handleLogout( } // If the request expiry is in the past, we consider it a logout. + // Zero expiry is handled in handleRegister() before calling this function. if req.Expiry.Before(time.Now()) { log.Debug(). Uint64("node.id", node.ID().Uint64()).