TechnitiumSoftware/DnsServer · error · DnsServerException
Cannot update record: TTL cannot be greater than SOA EXPIRE.
Error message
Cannot update record: TTL cannot be greater than SOA EXPIRE.
What it means
The update-path equivalent of error 501: PrimarySubDomainZone.UpdateRecord throws DnsServerException when newRecord.OriginalTtlValue exceeds _primaryZone.GetZoneSoaExpire(). The TTL-vs-EXPIRE invariant must hold for any persisted RRset, so the guard runs after the type-match and disabled-record checks and before TryDeleteRecord.
Source
Thrown at DnsServerCore/Dns/Zones/PrimarySubDomainZone.cs:239
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);
allDeletedRecords.AddRange(deletedRecords);
_primaryZone.CommitAndIncrementSerial(allDeletedRecords, addedRecords);
if (_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned)
_primaryZone.UpdateDnssecRecordsFor(this, oldRecord.Type);
_primaryZone.TriggerNotify();
break;
}View on GitHub (pinned to d0484b6c1e)
Solutions
- Clamp newRecord TTL to <= SOA EXPIRE before updating.
- Raise the SOA EXPIRE (via SetRecords on the SOA RRset) if a longer TTL is required.
- Normalize TTLs in any batch update pipeline against the live SOA EXPIRE.
Example fix
// before
zone.UpdateRecord(old, newHighTtlRecord);
// after
uint expire = zone.GetZoneSoaExpire();
if (newRecord.OriginalTtlValue > expire)
newRecord = newRecord.WithTtl(expire);
zone.UpdateRecord(old, newRecord); Defensive patterns
Strategy: validation
Validate before calling
uint expire = primaryZone.GetZoneSoaExpire();
if (newRecord.OriginalTtlValue > expire)
newRecord = newRecord.WithTtl(expire);
zone.UpdateRecord(oldRecord, newRecord); Type guard
static bool TtlFitsSoaExpire(DnsResourceRecord r, uint soaExpire) => r.OriginalTtlValue <= soaExpire;
Prevention
- Clamp new-record TTL to SOA EXPIRE before every update.
- Normalize TTLs in batch update jobs against live SOA EXPIRE.
- Raise SOA EXPIRE if longer TTLs are genuinely needed.
When it happens
Trigger: zone.UpdateRecord(old, new) where newRecord.OriginalTtlValue > zone SOA EXPIRE.
Common situations: Updating a record to a longer TTL imported from elsewhere; SOA EXPIRE lowered after records were already in place.
Related errors
- Cannot add record: TTL cannot be greater than SOA EXPIRE.
- Cannot update record: use SetRecords() for {oldRecord.Type}
- Zone does not contain SOA record.
- Cannot delete DNSSEC records.
- Cannot update DNSSEC records.
AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13).
Data as JSON: /api/errors/c1683103fee9baab.
Report an issue: GitHub.