TechnitiumSoftware/DnsServer · error · InvalidOperationException

Cannot update record: use SetRecords() for {type} record

Error message

Cannot update record: use SetRecords() for {type} record

What it means

AuthZone.UpdateRecord rejects SOA record updates with InvalidOperationException. The SOA (Start of Authority) record is a singleton that defines zone-wide parameters (serial, refresh, retry, expire, minimum) and has special semantics for zone transfers and DNSSEC. UpdateRecord performs a delete-then-add sequence that is unsafe for SOA; the library requires SetRecords() which atomically replaces the single SOA record.

Source

Thrown at DnsServerCore/Dns/Zones/AuthZone.cs:855

            AddRecord(record, out IReadOnlyList<DnsResourceRecord> addedRecords, out _);

            return addedRecords.Count > 0;
        }

        public virtual bool DeleteRecords(DnsResourceRecordType type)
        {
            return _entries.TryRemove(type, out _);
        }

        public virtual bool DeleteRecord(DnsResourceRecordType type, DnsResourceRecordData rdata)
        {
            return TryDeleteRecord(type, rdata, out _);
        }

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

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

            if (!DeleteRecord(oldRecord.Type, oldRecord.RDATA))
                throw new DnsWebServiceException("Cannot update record: the old record does not exists.");

            AddRecord(newRecord);
        }

        public virtual IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)
        {
            switch (type)
            {
                case DnsResourceRecordType.APP:
                case DnsResourceRecordType.FWD:
                case DnsResourceRecordType.NSEC:
                case DnsResourceRecordType.NSEC3:

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Use SetRecords(DnsResourceRecordType.SOA, [newSoaRecord]) to replace the SOA record atomically.
  2. Use the zone's dedicated SOA management methods if available (e.g., serial increment, timer adjustment APIs).
  3. Add a type guard before calling UpdateRecord to route SOA records to SetRecords.

Example fix

// before
zone.UpdateRecord(oldSoaRecord, newSoaRecord);
// throws: use SetRecords() for SOA record

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

Strategy: type-guard

Validate before calling

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

Type guard

static bool CanUpdateRecord(DnsResourceRecordType type)
    => type != DnsResourceRecordType.SOA;

Prevention

When it happens

Trigger: Calling UpdateRecord with oldRecord.Type == DnsResourceRecordType.SOA. This occurs when zone-management code attempts to modify SOA parameters (e.g., changing refresh interval or serial number) through the generic UpdateRecord API.

Common situations: Zone management UI/API that routes all record edits through a single UpdateRecord handler; scripts adjusting SOA timers; DNSSEC key rollover workflows that attempt SOA updates generically.

Related errors


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