juanfont/headscale · warning
node name is not unique
Error message
node name is not unique
What it means
Sentinel in hscontrol/db/node.go returned by RenameNode (node.go:200-201) and by the state layer (hscontrol/state/state.go:1054) when a node's given_name is already taken by another node. Given names are DNS labels under MagicDNS, so headscale enforces uniqueness before writing. API error mappers (hscontrol/api/v1/errors.go:32, v2/errors.go:77) map it to a conflict response.
Source
Thrown at hscontrol/db/node.go:32
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/hscontrol/util/zlog/zf"
"github.com/rs/zerolog/log"
"gorm.io/gorm"
"tailscale.com/types/key"
"tailscale.com/util/dnsname"
)
const (
NodeGivenNameHashLength = 8
NodeGivenNameTrimSize = 2
// defaultTestNodePrefix is the default hostname prefix for nodes created in tests.
defaultTestNodePrefix = "testnode"
)
// ErrNodeNameNotUnique is returned when a node name is not unique.
var ErrNodeNameNotUnique = errors.New("node name is not unique")
// preloadNode returns a session that eager-loads a node's AuthKey, the
// AuthKey's User, and the node's User.
func preloadNode(tx *gorm.DB) *gorm.DB {
return tx.
Preload("AuthKey").
Preload("AuthKey.User").
Preload("User")
}
var (
ErrNodeNotFound = errors.New("node not found")
ErrNodeRouteIsNotAvailable = errors.New("route is not available on node")
ErrNodeNotFoundRegistrationCache = errors.New(
"node not found in registration cache",
)
ErrCouldNotConvertNodeInterface = errors.New("failed to convert node interface")
)View on GitHub (pinned to 565fd254d0)
Solutions
- Pick a different name, or first rename/release the node currently holding it
- List nodes (`headscale nodes list`) to see existing given names before choosing
- Delete the stale node occupying the name if it is decommissioned
Example fix
# before headscale nodes rename --identifier 7 --new-name laptop # error: node name is not unique # after headscale nodes rename --identifier 7 --new-name laptop-2
Defensive patterns
Strategy: validation
Validate before calling
// before renaming, check uniqueness the same way the DB does
count, err := tx.Model(&types.Node{}).
Where("given_name = ? AND id != ?", newName, nodeID).Count(&new(int64)).Error Try / catch
if err := state.RenameNode(nodeID, newName); err != nil {
if errors.Is(err, state.ErrNodeNameNotUnique) {
// map to a 409-style conflict for API callers
return conflict("given_name already in use: " + newName)
}
return err
} Prevention
- Derive names deterministically and include a unique suffix when auto-naming
- Have API clients treat name conflicts as retryable-with-new-name, not as a server error
- Check `headscale nodes list` output in provisioning scripts before assigning a name
When it happens
Trigger: Calling `headscale nodes rename` (or the gRPC/API rename) with a name that another node already has as its given_name; concurrent registration of two nodes that normalize to the same given name.
Common situations: Renaming node B to node A's name to 'migrate' it; VM clones re-registering with identical hostnames so the generated given names collide during manual rename.
Related errors
- node name is not unique
- node not found
- route is not available on node
- failed to convert node interface
- error resolving host
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/bf82a713f7516ac5.
Report an issue: GitHub.