TechnitiumSoftware/DnsServer · error · DnsServerException

Failed to update Cluster options: only the Primary node can

Error message

Failed to update Cluster options: only the Primary node can update the Cluster options.

What it means

UpdateClusterOptions may only be called on the cluster Primary (GetSelfNode().Type == Primary). Cluster options are Primary-owned and replicated to Secondaries.

Source

Thrown at DnsServerCore/Cluster/ClusterManager.cs:905

                                                    allowedZones: true,
                                                    blockedZones: true,
                                                    blockLists: true,
                                                    apps: true,
                                                    scopes: false,
                                                    stats: false,
                                                    logs: false,
                                                    isConfigTransfer: true,
                                                    ifModifiedSince: ifModifiedSince,
                                                    includeZones: includeZones);
        }

        public void UpdateClusterOptions(ushort heartbeatRefreshIntervalSeconds, ushort heartbeatRetryIntervalSeconds, ushort configRefreshIntervalSeconds, ushort configRetryIntervalSeconds)
        {
            if (!ClusterInitialized)
                throw new DnsServerException("Failed to update Cluster options: the Cluster is not initialized.");

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

            if ((heartbeatRefreshIntervalSeconds < 10) || (heartbeatRefreshIntervalSeconds > 300))
                throw new ArgumentOutOfRangeException(nameof(heartbeatRefreshIntervalSeconds));

            if ((heartbeatRetryIntervalSeconds < 10) || (heartbeatRetryIntervalSeconds > 300))
                throw new ArgumentOutOfRangeException(nameof(heartbeatRetryIntervalSeconds));

            if ((configRefreshIntervalSeconds < 30) || (configRefreshIntervalSeconds > 3600))
                throw new ArgumentOutOfRangeException(nameof(configRefreshIntervalSeconds));

            if ((configRetryIntervalSeconds < 30) || (configRetryIntervalSeconds > 3600))
                throw new ArgumentOutOfRangeException(nameof(configRetryIntervalSeconds));

            if (configRefreshIntervalSeconds <= heartbeatRefreshIntervalSeconds)
                throw new ArgumentException("Failed to update Cluster options: The config refresh interval must be greater than the heartbeat refresh interval.");

            bool changed = false;

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Send option updates to the current Primary
  2. Rediscover the Primary after failover
  3. Use role-aware routing in front of the API

Example fix

// before
clusterManager.UpdateClusterOptions(hb, hbRetry, cfg, cfgRetry);

// after
if (clusterManager.GetSelfNode().Type != ClusterNodeType.Primary)
    return Redirect("Update options on the Primary node.");
clusterManager.UpdateClusterOptions(hb, hbRetry, cfg, cfgRetry);
Defensive patterns

Strategy: validation

Validate before calling

if (clusterManager.GetSelfNode().Type != ClusterNodeType.Primary)
    return BadRequest("Only the Primary can update cluster options.");
clusterManager.UpdateClusterOptions(hb, hbRetry, cfg, cfgRetry);

Try / catch

catch (DnsServerException ex) when (ex.Message.Contains("only the Primary node can update the Cluster options"))
{ /* rediscover Primary and retry */ }

Prevention

When it happens

Trigger: Calling the tuning endpoint on a Secondary; an LB routing to a non-Primary member; a demotion after the client cached the role.

Common situations: Failover flipping the Primary; client hitting a member by IP; multi-node deployments without role-aware routing.

Related errors


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