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
- Use zone.SetRecords(DnsResourceRecordType.SOA, new[] { newSoaRecord }) to replace the SOA RRset.
- Adjust SOA fields through the zone's dedicated SOA helper if available.
- 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
- Treat SOA as a singleton: always use SetRecords for it.
- In generic update helpers, branch SOA to the setter.
- Do not feed SOA rows through UpdateRecord.
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
- Cannot add record: TTL cannot be greater than SOA EXPIRE.
- Cannot delete DNSSEC records.
- Cannot update DNSSEC records.
- Old and new record types do not match.
- Cannot update record: disabling records in a signed zones is
AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13).
Data as JSON: /api/errors/0413ccc60f1aafb9.
Report an issue: GitHub.