TechnitiumSoftware/DnsServer · error · InvalidOperationException
Cannot update DNSSEC records.
Error message
Cannot update DNSSEC records.
What it means
Thrown by PrimarySubDomainZone.UpdateRecord when the record being updated is a DNSSEC type (DNSKEY/RRSIG/NSEC/NSEC3PARAM/NSEC3). DNSSEC RRs are derived from signing keys and cannot be updated through the generic record API without desynchronizing signatures. The guard raises InvalidOperationException early in the type switch.
Solutions
- Skip DNSSEC types in the update path; manage them via signing/key APIs.
- Unsign and re-sign if the DNSSEC data is genuinely stale.
- Re-export the zone without DNSSEC records before replaying edits.
Example fix
// before
zone.UpdateRecord(oldRrsig, newRrsig);
// after
if (!IsDnssecRecordType(oldRecord.Type))
zone.UpdateRecord(oldRecord, newRecord); Defensive patterns
Strategy: validation
Validate before calling
if (IsDnssecRecordType(oldRecord.Type)) return; // signer-managed zone.UpdateRecord(oldRecord, newRecord);
Type guard
static bool IsDnssecRecordType(DnsResourceRecordType t) =>
t == DnsResourceRecordType.DNSKEY || t == DnsResourceRecordType.RRSIG ||
t == DnsResourceRecordType.NSEC || t == DnsResourceRecordType.NSEC3PARAM ||
t == DnsResourceRecordType.NSEC3; Prevention
- Exclude DNSSEC types from generic update pipelines.
- Strip RRSIG/DNSKEY/NSEC lines from zone exports before replaying edits.
- Manage DNSSEC data only via sign/unsign/key-roll APIs.
When it happens
Trigger: zone.UpdateRecord(oldRecord, newRecord) with oldRecord.Type one of DNSKEY, RRSIG, NSEC, NSEC3PARAM, NSEC3.
Common situations: Re-importing a zone export that includes RRSIG/DNSKEY lines into the generic updater; tooling unaware that signed zones own these RRs.
Related errors
- Cannot delete DNSSEC records.
- Cannot update record: disabling records in a signed zones…
- Cannot convert to NSEC: the zone must be signed with NSEC3…
- Cannot sign zone: the zone is already signed.
- Cannot unsign zone: the is zone not signed.
AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13).
Data as JSON: /api/errors/4a960d2a63917a47.
Report an issue: GitHub.
Appendix: source
Thrown at DnsServerCore/Dns/Zones/PrimarySubDomainZone.cs:229
}
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.");
if (!TryDeleteRecord(oldRecord.Type, oldRecord.RDATA, out DnsResourceRecord deletedRecord))
throw new InvalidOperationException("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);View on GitHub (pinned to d0484b6c1e)