{"record":{"id":"10132d6fbe189305","repo":"TechnitiumSoftware/DnsServer","slug":"cannot-set-record-ttl-cannot-be-greater-than-soa-10132d","errorCode":null,"errorMessage":"Cannot set record: TTL cannot be greater than SOA EXPIRE.","messagePattern":"Cannot set record: TTL cannot be greater than SOA EXPIRE\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/PrimaryZone.cs","lineNumber":2543,"sourceCode":"                        break;\n                }\n            }\n\n            switch (type)\n            {\n                case DnsResourceRecordType.CNAME:\n                case DnsResourceRecordType.DS:\n                    throw new InvalidOperationException(\"Cannot set \" + type.ToString() + \" record at zone apex.\");\n\n                case DnsResourceRecordType.SOA:\n                    if ((records.Count != 1) || !records[0].Name.Equals(_name, StringComparison.OrdinalIgnoreCase))\n                        throw new InvalidOperationException(\"Invalid SOA record.\");\n\n                    DnsResourceRecord newSoaRecord = records[0];\n                    DnsSOARecordData newSoa = newSoaRecord.RDATA as DnsSOARecordData;\n\n                    if (newSoaRecord.OriginalTtlValue > newSoa.Expire)\n                        throw new DnsServerException(\"Cannot set record: TTL cannot be greater than SOA EXPIRE.\");\n\n                    if (newSoa.Retry > newSoa.Refresh)\n                        throw new DnsServerException(\"Cannot set record: SOA RETRY cannot be greater than SOA REFRESH.\");\n\n                    if (newSoa.Refresh > newSoa.Expire)\n                        throw new DnsServerException(\"Cannot set record: SOA REFRESH cannot be greater than SOA EXPIRE.\");\n\n                    //remove any record info except serial date scheme and comments\n                    bool useSoaSerialDateScheme;\n                    string comments;\n                    {\n                        SOARecordInfo recordInfo = newSoaRecord.GetAuthSOARecordInfo();\n\n                        useSoaSerialDateScheme = recordInfo.UseSoaSerialDateScheme;\n                        comments = recordInfo.Comments;\n                    }\n\n                    newSoaRecord.Tag = null; //remove old record info","sourceCodeStart":2525,"sourceCodeEnd":2561,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/PrimaryZone.cs#L2525-L2561","documentation":"Thrown by PrimaryZone.SetRecords() in the SOA case when the new SOA record's TTL (OriginalTtlValue) is greater than the new SOA EXPIRE value. Per DNS semantics, a resolver could cache the record longer than the EXPIRE window in which secondary servers are guaranteed to have valid data, causing stale answers; the server enforces TTL <= EXPIRE.","triggerScenarios":"Calling SetRecords with an SOA whose OriginalTtlValue > newSoa.Expire (e.g. TTL 86400 but EXPIRE 3600).","commonSituations":"Copying a high-TTL SOA from another zone into one with a short EXPIRE; lowering EXPIRE for faster failover without also lowering the SOA TTL; template values where TTL and EXPIRE were set independently and inconsistently.","solutions":["Set the SOA TTL to a value <= the SOA EXPIRE before calling SetRecords.","If you need a long TTL, raise EXPIRE to at least that value first (subject to the REFRESH <= EXPIRE constraint).","Validate OriginalTtlValue <= Expire on the constructed SOA before submission."],"exampleFix":"// before\nvar soa = new DnsSOARecordData { Expire = 3600 };\nzone.SetRecords(DnsResourceRecordType.SOA, new[] { BuildSoaRecord(ttl: 86400, soa) }); // throws\n\n// after\nvar soa = new DnsSOARecordData { Expire = 86400 };\nzone.SetRecords(DnsResourceRecordType.SOA, new[] { BuildSoaRecord(ttl: 3600, soa) });","handlingStrategy":"validation","validationCode":"if (type == DnsResourceRecordType.SOA)\n{\n    var soa = (DnsSOARecordData)records[0].RDATA;\n    if (records[0].OriginalTtlValue > soa.Expire)\n        throw new ArgumentException(\"SOA TTL must be <= SOA EXPIRE.\");\n}\n\nzone.SetRecords(type, records);","typeGuard":"static bool SoaTtlWithinExpire(DnsResourceRecord soaRecord)\n{\n    var soa = (DnsSOARecordData)soaRecord.RDATA;\n    return soaRecord.OriginalTtlValue <= soa.Expire;\n}","tryCatchPattern":"try { zone.SetRecords(type, records); }\ncatch (DnsServerException ex) when (ex.Message == \"Cannot set record: TTL cannot be greater than SOA EXPIRE.\")\n{ Log.Error(\"Lower the SOA TTL or raise SOA EXPIRE.\"); }","preventionTips":["Keep SOA TTL <= EXPIRE in templates.","When lowering EXPIRE, re-check the SOA TTL.","Validate all three SOA timer invariants together before submission."],"tags":["dns","record","soa","ttl","expire","validation"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}