{"record":{"id":"fb1ad2d43421d509","repo":"TechnitiumSoftware/DnsServer","slug":"failed-to-join-cluster-the-zone-clustercatalogd","errorCode":null,"errorMessage":"Failed to join Cluster: the zone '{clusterCatalogDomain}' already exists. Please delete the '{clusterCatalogDomain}' zone and try again.","messagePattern":"Failed to join Cluster: the zone '(.+?)' already exists\\. Please delete the '(.+?)' zone and try again\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Cluster/ClusterManager.cs","lineNumber":1376,"sourceCode":"            }\n            catch (TwoFactorAuthRequiredHttpApiClientException ex)\n            {\n                throw new TwoFactorAuthRequiredWebServiceException(\"Failed to join Cluster: two-factor authentication is required by the Primary node user account.\", ex);\n            }\n\n            try\n            {\n                //get cluster info\n                ClusterInfo primaryNodeClusterInfo = await primaryNodeApiClient.GetClusterStateAsync(cancellationToken: cancellationToken);\n\n                //do validations\n                if (!primaryNodeClusterInfo.ClusterInitialized)\n                    throw new DnsServerException(\"Failed to join Cluster: the Primary node does not have a Cluster initialized.\");\n\n                string clusterCatalogDomain = \"cluster-catalog.\" + primaryNodeClusterInfo.ClusterDomain;\n\n                if (_dnsWebService.DnsServer.AuthZoneManager.GetAuthZoneInfo(clusterCatalogDomain) is not null)\n                    throw new DnsServerException($\"Failed to join Cluster: the zone '{clusterCatalogDomain}' already exists. Please delete the '{clusterCatalogDomain}' zone and try again.\");\n\n                if (_dnsWebService.DnsServer.AuthZoneManager.GetAuthZoneInfo(primaryNodeClusterInfo.ClusterDomain) is not null)\n                    throw new DnsServerException($\"Failed to join Cluster: the zone '{primaryNodeClusterInfo.ClusterDomain}' already exists. Please delete the '{primaryNodeClusterInfo.ClusterDomain}' zone and try again.\");\n\n                //create self node\n                string serverDomain = _dnsWebService.DnsServer.ServerDomain;\n                if (!serverDomain.EndsWith(\".\" + primaryNodeClusterInfo.ClusterDomain, StringComparison.OrdinalIgnoreCase))\n                {\n                    int x = serverDomain.IndexOf('.');\n                    if (x < 0)\n                        serverDomain = serverDomain + \".\" + primaryNodeClusterInfo.ClusterDomain;\n                    else\n                        serverDomain = string.Concat(serverDomain.AsSpan(0, x), \".\", primaryNodeClusterInfo.ClusterDomain);\n                }\n\n                Uri secondaryNodeUrl = new Uri($\"https://{serverDomain}:{_dnsWebService.WebServiceTlsPort}/\");\n\n                ClusterNode selfSecondaryNode = new ClusterNode(this, RandomNumberGenerator.GetInt32(int.MaxValue), secondaryNodeUrl, secondaryNodeIpAddresses, ClusterNodeType.Secondary, ClusterNodeState.Self);","sourceCodeStart":1358,"sourceCodeEnd":1394,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Cluster/ClusterManager.cs#L1358-L1394","documentation":"Before joining, the Secondary checks its local zones and finds 'cluster-catalog.<clusterDomain>' already exists. Joining would create this catalog zone, so a pre-existing one (from a prior or aborted join) blocks the operation.","triggerScenarios":"Re-joining after a failed or partial join left the catalog zone behind; importing a config that already had the catalog zone; a leftover zone from a different cluster.","commonSituations":"Retry of join after a crash; restoring from backup that included cluster zones; partial cleanup after leaving a cluster.","solutions":["Delete the existing 'cluster-catalog.<clusterDomain>' zone, then retry join","Fully leave/reset the previous cluster to clean up catalog zones","Verify no stale catalog zones remain before join"],"exampleFix":"// before\nawait clusterManager.InitializeAndJoinClusterAsync(ips, primaryUrl, user, pass);\n\n// after\nvar catalog = \"cluster-catalog.\" + info.ClusterDomain;\nif (dnsServer.AuthZoneManager.GetAuthZoneInfo(catalog) is not null)\n    dnsServer.AuthZoneManager.DeleteZone(catalog);\nawait clusterManager.InitializeAndJoinClusterAsync(ips, primaryUrl, user, pass);","handlingStrategy":"validation","validationCode":"string catalog = \"cluster-catalog.\" + info.ClusterDomain;\nif (dnsServer.AuthZoneManager.GetAuthZoneInfo(catalog) is not null)\n    return Conflict($\"Zone '{catalog}' exists; delete it before joining.\");\nawait clusterManager.InitializeAndJoinClusterAsync(ips, primaryUrl, user, pass);","typeGuard":null,"tryCatchPattern":"catch (DnsServerException ex) when (ex.Message.Contains(\"cluster-catalog\") && ex.Message.Contains(\"already exists\"))\n{ /* offer to delete the stale catalog zone then retry */ }","preventionTips":["Clean up cluster-catalog zones after leaving or aborting a join","Check for pre-existing cluster zones before joining"],"tags":["cluster","zones","lifecycle","join","validation","dns-server"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}