{"record":{"id":"c1683103fee9baab","repo":"TechnitiumSoftware/DnsServer","slug":"cannot-update-record-ttl-cannot-be-greater-than-s-c16831","errorCode":null,"errorMessage":"Cannot update record: TTL cannot be greater than SOA EXPIRE.","messagePattern":"Cannot update record: TTL cannot be greater than SOA EXPIRE\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/PrimarySubDomainZone.cs","lineNumber":239,"sourceCode":"                case DnsResourceRecordType.SOA:\n                    throw new InvalidOperationException(\"Cannot update record: use SetRecords() for \" + oldRecord.Type.ToString() + \" record.\");\n\n                case DnsResourceRecordType.DNSKEY:\n                case DnsResourceRecordType.RRSIG:\n                case DnsResourceRecordType.NSEC:\n                case DnsResourceRecordType.NSEC3PARAM:\n                case DnsResourceRecordType.NSEC3:\n                    throw new InvalidOperationException(\"Cannot update DNSSEC records.\");\n\n                default:\n                    if (oldRecord.Type != newRecord.Type)\n                        throw new InvalidOperationException(\"Old and new record types do not match.\");\n\n                    if ((_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned) && newRecord.GetAuthGenericRecordInfo().Disabled)\n                        throw new DnsServerException(\"Cannot update record: disabling records in a signed zones is not supported.\");\n\n                    if (newRecord.OriginalTtlValue > _primaryZone.GetZoneSoaExpire())\n                        throw new DnsServerException(\"Cannot update record: TTL cannot be greater than SOA EXPIRE.\");\n\n                    if (!TryDeleteRecord(oldRecord.Type, oldRecord.RDATA, out DnsResourceRecord deletedRecord))\n                        throw new InvalidOperationException(\"Cannot update record: the record does not exists to be updated.\");\n\n                    AddRecord(newRecord, out IReadOnlyList<DnsResourceRecord> addedRecords, out IReadOnlyList<DnsResourceRecord> deletedRecords);\n\n                    List<DnsResourceRecord> allDeletedRecords = new List<DnsResourceRecord>(deletedRecords.Count + 1);\n                    allDeletedRecords.Add(deletedRecord);\n                    allDeletedRecords.AddRange(deletedRecords);\n\n                    _primaryZone.CommitAndIncrementSerial(allDeletedRecords, addedRecords);\n\n                    if (_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned)\n                        _primaryZone.UpdateDnssecRecordsFor(this, oldRecord.Type);\n\n                    _primaryZone.TriggerNotify();\n                    break;\n            }","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/PrimarySubDomainZone.cs#L221-L257","documentation":"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.","triggerScenarios":"zone.UpdateRecord(old, new) where newRecord.OriginalTtlValue > zone SOA EXPIRE.","commonSituations":"Updating a record to a longer TTL imported from elsewhere; SOA EXPIRE lowered after records were already in place.","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."],"exampleFix":"// before\nzone.UpdateRecord(old, newHighTtlRecord);\n\n// after\nuint expire = zone.GetZoneSoaExpire();\nif (newRecord.OriginalTtlValue > expire)\n    newRecord = newRecord.WithTtl(expire);\nzone.UpdateRecord(old, newRecord);","handlingStrategy":"validation","validationCode":"uint expire = primaryZone.GetZoneSoaExpire();\nif (newRecord.OriginalTtlValue > expire)\n    newRecord = newRecord.WithTtl(expire);\nzone.UpdateRecord(oldRecord, newRecord);","typeGuard":"static bool TtlFitsSoaExpire(DnsResourceRecord r, uint soaExpire) => r.OriginalTtlValue <= soaExpire;","tryCatchPattern":null,"preventionTips":["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."],"tags":["csharp","dns","ttl","soa","validation","technitium-dns"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}