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
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.
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)
Solutions
- Route SOA edits through zone.SetRecords(DnsResourceRecordType.SOA, new[] { newSoaRecord }).
- In a generic editor, branch on type: SOA -> SetRecords, else UpdateRecord.
- 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
- Make generic record editors branch SOA to SetRecords.
- Build SOA serial bumps by replacing the full RRset.
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
- Cannot delete SOA record.
- Cannot update DNSSEC records.
- Cannot update record: TTL cannot be greater than SOA EXPIRE.
- Cannot add record: TTL cannot be greater than SOA EXPIRE.
- Cannot delete DNSSEC records.
AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13).
Data as JSON: /api/errors/be63b1057f9eca61.
Report an issue: GitHub.