TechnitiumSoftware/DnsServer · error · InvalidOperationException

Cannot update record: use SetRecords() for

Error message

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

What it means

Thrown by PrimaryZone.UpdateRecord when oldRecord.Type is SOA. Because a zone has exactly one SOA and it carries serial/timing fields, UpdateRecord refuses it and points the caller to SetRecords(), which replaces the whole SOA RRset atomically. InvalidOperationException signals the wrong API was chosen.

Solutions

  1. Route SOA edits through zone.SetRecords(DnsResourceRecordType.SOA, new[] { newSoaRecord }).
  2. In a generic editor, branch on type: SOA -> SetRecords, else UpdateRecord.
  3. When bumping the serial, build a new SOA RRset and call SetRecords.

Example fix

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

// 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 RequiresSetRecords(DnsResourceRecordType t) =>
    t == DnsResourceRecordType.SOA;

Try / catch

null

Prevention

When it happens

Trigger: Calling zone.UpdateRecord(oldSoaRecord, newSoaRecord) on a PrimaryZone.

Common situations: Generic 'update record' UI that routes all edits through UpdateRecord; SOA serial-bump logic that tries to mutate the SOA in place.

Related errors


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

Appendix: source

Thrown at DnsServerCore/Dns/Zones/PrimaryZone.cs:2745

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

                        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 ((_dnssecStatus != AuthZoneDnssecStatus.Unsigned) && newRecord.GetAuthGenericRecordInfo().Disabled)
                        throw new DnsServerException("Cannot update record: disabling records in a signed zones is not supported.");

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

View on GitHub (pinned to d0484b6c1e)