TechnitiumSoftware/DnsServer · error · InvalidOperationException

Cannot update record: use SetRecords() for {oldRecord.Type}

Error message

Cannot update record: use SetRecords() for {oldRecord.Type} record.

What it means

Thrown by PrimarySubDomainZone.UpdateRecord when the old record is of type SOA. A zone has exactly one SOA and its fields (serial, timers, nameservers) are interdependent, so the generic update-in-place path is unsafe. The guard forces callers to use SetRecords for SOA, raising InvalidOperationException before any comparison of old/new.

Source

Thrown at DnsServerCore/Dns/Zones/PrimarySubDomainZone.cs:222

                        if (_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned)
                            _primaryZone.UpdateDnssecRecordsFor(this, type);

                        _primaryZone.TriggerNotify();

                        return true;
                    }

                    return false;
            }
        }

        public override void UpdateRecord(DnsResourceRecord oldRecord, DnsResourceRecord newRecord)
        {
            switch (oldRecord.Type)
            {
                case DnsResourceRecordType.SOA:
                    throw new InvalidOperationException("Cannot update record: use SetRecords() for " + oldRecord.Type.ToString() + " record.");

                case DnsResourceRecordType.DNSKEY:
                case DnsResourceRecordType.RRSIG:
                case DnsResourceRecordType.NSEC:
                case DnsResourceRecordType.NSEC3PARAM:
                case DnsResourceRecordType.NSEC3:
                    throw new InvalidOperationException("Cannot update DNSSEC records.");

                default:
                    if (oldRecord.Type != newRecord.Type)
                        throw new InvalidOperationException("Old and new record types do not match.");

                    if ((_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned) && newRecord.GetAuthGenericRecordInfo().Disabled)
                        throw new DnsServerException("Cannot update record: disabling records in a signed zones is not supported.");

                    if (newRecord.OriginalTtlValue > _primaryZone.GetZoneSoaExpire())
                        throw new DnsServerException("Cannot update record: TTL cannot be greater than SOA EXPIRE.");

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Use zone.SetRecords(DnsResourceRecordType.SOA, new[] { newSoaRecord }) to replace the SOA RRset.
  2. Adjust SOA fields through the zone's dedicated SOA helper if available.
  3. Route SOA edits out of any generic per-record update loop.

Example fix

// before
zone.UpdateRecord(oldSoa, newSoa); // throws

// after
zone.SetRecords(DnsResourceRecordType.SOA, new[] { newSoa });
Defensive patterns

Strategy: validation

Validate before calling

if (oldRecord.Type == DnsResourceRecordType.SOA)
    zone.SetRecords(DnsResourceRecordType.SOA, new[] { newRecord });
else
    zone.UpdateRecord(oldRecord, newRecord);

Type guard

static bool IsSingletonRecordType(DnsResourceRecordType t) => t == DnsResourceRecordType.SOA;

Prevention

When it happens

Trigger: zone.UpdateRecord(oldSoaRecord, newSoaRecord) where oldRecord.Type == DnsResourceRecordType.SOA. The interpolated message embeds 'SOA'.

Common situations: Automating SOA edits (e.g., bumping EXPIRE/refresh) via the generic update API instead of the SOA setter; UI that funnels all edits through UpdateRecord.

Related errors


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