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
- Run JoinCluster on the cluster Primary node.
- 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
- Send all add-secondary requests to the Primary node.
- Pin cluster-management API calls to the Primary in load-balanced environments.
- Check GetSelfNode().Type before calling JoinCluster.
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
- Failed to delete Cluster: only a Primary node can delete the
- Failed to ask Secondary node to leave: only a Primary node c
- Failed to delete Secondary node: only a Primary node can del
- Failed to update Secondary node: only a Primary node can upd
- Failed to transfer configuration: only the Primary node can
AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13).
Data as JSON: /api/errors/139b95d68a303b1e.
Report an issue: GitHub.