TechnitiumSoftware/DnsServer · error · DnsServerException

Failed to delete Cluster: please remove all Secondary nodes

Error message

Failed to delete Cluster: please remove all Secondary nodes before deleting the Cluster.

What it means

Thrown by DeleteCluster when forceDelete is false and more than one node exists in the cluster (_clusterNodes.Count > 1). This safety guard prevents orphaning Secondary nodes that would lose their Primary and be left in an inconsistent state. The Primary node alone counts as 1, so any Secondary pushes the count above the threshold.

Source

Thrown at DnsServerCore/Cluster/ClusterManager.cs:678

            //finalize
            _dnsWebService.DnsServer.ServerDomain = selfPrimaryNode.Name;

            //save all changes
            _dnsWebService.DnsServer.SaveConfigFile(true);
            _dnsWebService.AuthManager.SaveConfigFile(true);
            SaveConfigFile();
        }

        public void DeleteCluster(bool forceDelete)
        {
            if (!ClusterInitialized)
                throw new DnsServerException("Failed to delete Cluster: the Cluster is not initialized.");

            if (GetSelfNode().Type != ClusterNodeType.Primary)
                throw new DnsServerException("Failed to delete Cluster: only a Primary node can delete the Cluster.");

            if (!forceDelete && (_clusterNodes.Count > 1))
                throw new DnsServerException("Failed to delete Cluster: please remove all Secondary nodes before deleting the Cluster.");

            DeleteAllClusterConfig();
        }

        public ClusterNode JoinCluster(int secondaryNodeId, Uri secondaryNodeUrl, IReadOnlyList<IPAddress> secondaryNodeIpAddresses, X509Certificate2 secondaryNodeCertificate)
        {
            if (!ClusterInitialized)
                throw new DnsServerException("Failed to add Secondary node: the Cluster is not initialized.");

            if (GetSelfNode().Type != ClusterNodeType.Primary)
                throw new DnsServerException("Failed to add Secondary node: only a Primary node can add a Secondary node to the Cluster.");

            string secondaryNodeDomain = secondaryNodeUrl.Host.ToLowerInvariant();

            if (!secondaryNodeDomain.EndsWith("." + _clusterDomain, StringComparison.OrdinalIgnoreCase))
                throw new DnsServerException("Failed to add Secondary node: the Secondary node domain name must be a subdomain of the Cluster domain name.");

            IReadOnlyDictionary<int, ClusterNode> existingClusterNodes = _clusterNodes;

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Remove all Secondary nodes first using DeleteSecondaryNode or AskSecondaryNodeToLeaveClusterAsync, then call DeleteCluster(false).
  2. If you intentionally want to force teardown with Secondaries still present, call DeleteCluster(true) to bypass the guard.

Example fix

// before
_dnsWebService.ClusterManager.DeleteCluster(false);
// after — drain secondaries first
foreach (var node in _dnsWebService.ClusterManager.ClusterNodes)
{
    if (node.Value.Type == ClusterNodeType.Secondary)
        _dnsWebService.ClusterManager.DeleteSecondaryNode(node.Key);
}
_dnsWebService.ClusterManager.DeleteCluster(false);
Defensive patterns

Strategy: validation

Validate before calling

// Drain all secondaries before deleting, or force-delete
foreach (var node in _dnsWebService.ClusterManager.ClusterNodes)
{
    if (node.Value.Type == ClusterNodeType.Secondary)
        _dnsWebService.ClusterManager.DeleteSecondaryNode(node.Key);
}
_dnsWebService.ClusterManager.DeleteCluster(false);
// or: _dnsWebService.ClusterManager.DeleteCluster(true); // force

Type guard

static bool IsClusterDrained(ClusterManager cm)
    => cm.ClusterNodes.Count(n => n.Value.Type == ClusterNodeType.Secondary) == 0;

Try / catch

try
{
    _dnsWebService.ClusterManager.DeleteCluster(false);
}
catch (DnsServerException ex) when (ex.Message.Contains("remove all Secondary nodes"))
{
    // either drain secondaries and retry, or call DeleteCluster(true) to force
    throw;
}

Prevention

When it happens

Trigger: DeleteCluster(false) is called when _clusterNodes contains the Primary plus one or more Secondary nodes. The guard at line 677 fires after the ClusterInitialized and Primary-node checks pass.

Common situations: Attempting a cluster teardown without first draining Secondary nodes; a scripted cleanup that skips the node-removal step.

Related errors


AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13). Data as JSON: /api/errors/f9710f98a1c18faa. Report an issue: GitHub.