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

Thrown by ForwarderZone.UpdateRecord() when newRecord.OriginalTtlValue exceeds GetZoneSoaExpire(). The check enforces RFC 1035 SOA semantics: a record's TTL must not outlive the zone's SOA EXPIRE, because slaves would be permitted to serve stale data past the point the zone is considered stale. The default forwarder-zone dummy SOA sets Expire to 604800 (7 days). This is a DnsServerException (a domain/operational error, surfaced as an HTTP failure to API clients), so it is catchable.

Source

Thrown at DnsServerCore/Dns/Zones/ForwarderZone.cs:250

                    }

                    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");

                default:
                    if (oldRecord.Type != newRecord.Type)
                        throw new InvalidOperationException("Old and new record types do not match.");

                    if (newRecord.OriginalTtlValue > 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 DnsServerException("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);

                    CommitAndIncrementSerial(allDeletedRecords, addedRecords);

                    TriggerNotify();
                    break;
            }
        }

        public override IReadOnlyList<DnsResourceRecord> QueryRecords(DnsResourceRecordType type, bool dnssecOk)

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Lower newRecord.OriginalTtlValue to <= GetZoneSoaExpire() (default 604800 seconds for a forwarder zone).
  2. If a longer TTL is required, raise the forwarder zone's SOA EXPIRE via SetRecords(SOA, ...) first.
  3. Clamp incoming TTLs at your import boundary: ttl = Math.Min(ttl, (int)zone.GetZoneSoaExpire()).

Example fix

// before
newRecord = new DnsResourceRecord(name, type, cls, importedTtl, rdata);
zone.UpdateRecord(oldRecord, newRecord);

// after
uint maxTtl = zone.GetZoneSoaExpire();
uint safeTtl = Math.Min(importedTtl, maxTtl);
newRecord = new DnsResourceRecord(name, type, cls, safeTtl, rdata);
zone.UpdateRecord(oldRecord, newRecord);
Defensive patterns

Strategy: validation

Validate before calling

uint maxTtl = zone.GetZoneSoaExpire();
if (newRecord.OriginalTtlValue > maxTtl)
    newRecord = new DnsResourceRecord(newRecord.Name, newRecord.Type, newRecord.Class, maxTtl, newRecord.RDATA);
zone.UpdateRecord(oldRecord, newRecord);

Type guard

static bool IsTtlWithinSoaExpire(DnsResourceRecord rec, uint soaExpire) => rec.OriginalTtlValue <= soaExpire;

Try / catch

try { zone.UpdateRecord(oldRecord, newRecord); }
catch (DnsServerException ex) when (ex.Message.Contains("SOA EXPIRE")) { /* clamp TTL and retry */ }

Prevention

When it happens

Trigger: zone.UpdateRecord(oldRecord, newRecord) where newRecord.OriginalTtlValue (the un-capped TTL before zone-minimum clamping) > the zone's SOA EXPIRE value. Triggered by importing records with very high TTLs, or by raising the record TTL without first raising SOA EXPIRE.

Common situations: Bulk import of external zone data with TTLs like 1209600 (14d); copying records from a primary zone whose SOA EXPIRE is larger than the forwarder's 604800; misreading OriginalTtlValue vs the already-clamped display TTL.

Related errors


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