TechnitiumSoftware/DnsServer · error · DnsServerException

Failed to add Secondary node: only a Primary node can add a

Error message

Failed to add Secondary node: only a Primary node can add a Secondary node to the Cluster.

What it means

Thrown by JoinCluster when the current server's self node is not the Primary node. Secondary nodes are added to the cluster by the Primary, which owns the authoritative node list and is responsible for writing NS/A records and catalog zone updates. A Secondary cannot add another Secondary.

Source

Thrown at DnsServerCore/Cluster/ClusterManager.cs:689

            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;

            //validate for duplicate names
            foreach (KeyValuePair<int, ClusterNode> existingClusterNode in existingClusterNodes)
            {
                if (existingClusterNode.Value.Name.Equals(secondaryNodeUrl.Host, StringComparison.OrdinalIgnoreCase))
                    throw new DnsServerException("Failed to add Secondary node: A node with the same DNS Server Domain Name already exists in the Cluster. Please try again after changing the Secondary node's DNS Server Domain Name.");
            }

            //add secondary node to cluster nodes
            ClusterNode secondaryNode = new ClusterNode(this, secondaryNodeId, secondaryNodeUrl, secondaryNodeIpAddresses, ClusterNodeType.Secondary, ClusterNodeState.Unknown);
            Dictionary<int, ClusterNode> updatedClusterNodes = new Dictionary<int, ClusterNode>(existingClusterNodes.Count + 1);

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Run JoinCluster on the cluster Primary node.
  2. Verify GetSelfNode().Type == ClusterNodeType.Primary before calling.

Example fix

// before
_dnsWebService.ClusterManager.JoinCluster(nodeId, nodeUrl, nodeIps, cert);
// after
if (_dnsWebService.ClusterManager.GetSelfNode().Type != ClusterNodeType.Primary)
    throw new InvalidOperationException("JoinCluster must be called on the Primary node.");
_dnsWebService.ClusterManager.JoinCluster(nodeId, nodeUrl, nodeIps, cert);
Defensive patterns

Strategy: validation

Validate before calling

if (_dnsWebService.ClusterManager.GetSelfNode().Type != ClusterNodeType.Primary)
    throw new InvalidOperationException("JoinCluster must be called on the Primary node.");
_dnsWebService.ClusterManager.JoinCluster(nodeId, nodeUrl, nodeIps, cert);

Type guard

static bool IsPrimaryNode(ClusterManager cm)
    => cm.ClusterInitialized && cm.GetSelfNode().Type == ClusterNodeType.Primary;

Try / catch

try
{
    _dnsWebService.ClusterManager.JoinCluster(nodeId, nodeUrl, nodeIps, cert);
}
catch (DnsServerException ex) when (ex.Message.Contains("only a Primary node can add"))
{
    throw new InvalidOperationException("Redirect this request to the cluster Primary node.", ex);
}

Prevention

When it happens

Trigger: JoinCluster is called from a server whose GetSelfNode().Type is not ClusterNodeType.Primary. The guard at line 688 fires after the ClusterInitialized check.

Common situations: The add-secondary API request is routed to or executed on a Secondary server rather than the Primary; misconfigured load balancer sending cluster-management traffic to a Secondary backend.

Related errors


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