TechnitiumSoftware/DnsServer · error · InvalidOperationException

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

Error message

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

What it means

Thrown by ForwarderZone.UpdateRecord() when oldRecord.Type is SOA. SOA records are special single-valued apex records whose semantics (serial, refresh, retry, expire, minimum, name server, responsible person) cannot be patched via the generic add/delete UpdateRecord flow; they must be atomically replaced via SetRecords(SOA, ...), which validates TTL<=Expire, Retry<=Refresh, Refresh<=Expire, and rewrites the responsible person to 'invalid'. The message echoes the offending type so the caller knows exactly which record type to redirect.

Source

Thrown at DnsServerCore/Dns/Zones/ForwarderZone.cs:243

                    if (TryDeleteRecord(type, rdata, out DnsResourceRecord deletedRecord))
                    {
                        CommitAndIncrementSerial([deletedRecord]);

                        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");

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

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

                    if (!TryDeleteRecord(oldRecord.Type, oldRecord.RDATA, out DnsResourceRecord deletedRecord))
                        throw new DnsServerException("Cannot update record: the record does not exists to be updated.");

                    AddRecord(newRecord, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords);

                    List<DnsResourceRecord> allDeletedRecords = new List<DnsResourceRecord>(deletedRecords.Count + 1);
                    allDeletedRecords.Add(deletedRecord);
                    allDeletedRecords.AddRange(deletedRecords);

                    CommitAndIncrementSerial(allDeletedRecords, addedRecords);

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Route SOA edits through SetRecords(DnsResourceRecordType.SOA, new[] { newSoaRecord }) instead of UpdateRecord.
  2. Branch in your API/UI layer: if (oldRecord.Type == DnsResourceRecordType.SOA) zone.SetRecords(oldRecord.Type, new[] { newRecord }); else zone.UpdateRecord(oldRecord, newRecord);
  3. Ensure the new SOA passes SetRecords validation (TTL <= Expire, Retry <= Refresh, Refresh <= Expire).

Example fix

// before
zone.UpdateRecord(oldRecord, newRecord);

// after
if (oldRecord.Type == DnsResourceRecordType.SOA)
    zone.SetRecords(DnsResourceRecordType.SOA, new[] { newRecord });
else
    zone.UpdateRecord(oldRecord, newRecord);
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 RequiresSetRecordsInsteadOfUpdate(DnsResourceRecordType type) => type == DnsResourceRecordType.SOA;

Try / catch

try { zone.UpdateRecord(oldRecord, newRecord); }
catch (InvalidOperationException ex) when (oldRecord.Type == DnsResourceRecordType.SOA) { zone.SetRecords(DnsResourceRecordType.SOA, new[] { newRecord }); }

Prevention

When it happens

Trigger: Calling zone.UpdateRecord(oldSoaRecord, newSoaRecord) where oldRecord.Type == DnsResourceRecordType.SOA on a ForwarderZone. Happens when generic record-edit UI/API code passes an SOA row into the same update handler used for A/AAAA/CNAME records.

Common situations: A generic 'edit record' API endpoint that does not branch on record type; an import/sync routine that round-trips SOA through the update path; clients editing SOA timers (refresh/retry/expire) via the wrong endpoint.

Related errors


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