{"record":{"id":"fb7c6465140f38c0","repo":"TechnitiumSoftware/DnsServer","slug":"cannot-set-records-ttl-cannot-be-greater-than-soa-fb7c64","errorCode":null,"errorMessage":"Cannot set records: TTL cannot be greater than SOA EXPIRE.","messagePattern":"Cannot set records: TTL cannot be greater than SOA EXPIRE\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/PrimarySubDomainZone.cs","lineNumber":94,"sourceCode":"\n            switch (type)\n            {\n                case DnsResourceRecordType.SOA:\n                    throw new InvalidOperationException(\"Cannot set SOA record on sub domain.\");\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 set DNSSEC records.\");\n\n                case DnsResourceRecordType.FWD:\n                    throw new DnsServerException(\"The record type is not supported by primary zones.\");\n\n                default:\n                    if (records[0].OriginalTtlValue > _primaryZone.GetZoneSoaExpire())\n                        throw new DnsServerException(\"Cannot set records: TTL cannot be greater than SOA EXPIRE.\");\n\n                    if (!TrySetRecords(type, records, out IReadOnlyList<DnsResourceRecord> deletedRecords))\n                        throw new DnsServerException(\"Cannot set records. Please try again.\");\n\n                    _primaryZone.CommitAndIncrementSerial(deletedRecords, records);\n\n                    if (_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned)\n                        _primaryZone.UpdateDnssecRecordsFor(this, type);\n\n                    _primaryZone.TriggerNotify();\n                    break;\n            }\n        }\n\n        public override bool AddRecord(DnsResourceRecord record)\n        {\n            if (_primaryZone.DnssecStatus != AuthZoneDnssecStatus.Unsigned)\n            {","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/PrimarySubDomainZone.cs#L76-L112","documentation":"Thrown by PrimarySubDomainZone.SetRecords() in the default case when records[0].OriginalTtlValue exceeds _primaryZone.GetZoneSoaExpire(). Enforces RFC 1035: a record TTL must not exceed the SOA EXPIRE so that secondary servers do not serve data beyond the zone's authoritative lifetime. The check uses the parent primary zone's SOA EXPIRE (the sub-domain shares the parent SOA). DnsServerException, catchable; same rule appears in ForwarderZone and PrimaryZone.","triggerScenarios":"subZone.SetRecords(type, records) where the first record's OriginalTtlValue > the parent primary zone's SOA EXPIRE. Triggered by importing high-TTL records or by lowering the parent SOA EXPIRE below existing record TTLs.","commonSituations":"Importing records with TTL 1209600+ into a zone whose SOA EXPIRE is the default; tightening SOA EXPIRE after the fact; copying records from a zone with a larger EXPIRE.","solutions":["Cap the record TTL to <= _primaryZone.GetZoneSoaExpire() before calling SetRecords.","Raise the parent primary zone's SOA EXPIRE first if a longer TTL is required (via primaryZone.SetRecords(SOA,...)).","Clamp TTLs at the import boundary using the parent zone's current SOA EXPIRE."],"exampleFix":"// before\nsubZone.SetRecords(type, records);\n\n// after\nuint maxTtl = _primaryZone.GetZoneSoaExpire();\nvar clamped = records.Select(r => r.OriginalTtlValue > maxTtl\n    ? new DnsResourceRecord(r.Name, r.Type, r.Class, maxTtl, r.RDATA) : r).ToList();\nsubZone.SetRecords(type, clamped);","handlingStrategy":"validation","validationCode":"uint maxTtl = _primaryZone.GetZoneSoaExpire();\nif (records[0].OriginalTtlValue > maxTtl)\n    records = records.Select(r => new DnsResourceRecord(r.Name, r.Type, r.Class, maxTtl, r.RDATA)).ToList();\nzone.SetRecords(type, records);","typeGuard":"static bool IsTtlWithinSoaExpire(IReadOnlyList<DnsResourceRecord> records, uint soaExpire) => records.Count > 0 && records[0].OriginalTtlValue <= soaExpire;","tryCatchPattern":"try { zone.SetRecords(type, records); }\ncatch (DnsServerException ex) when (ex.Message.Contains(\"SOA EXPIRE\")) { /* clamp TTL to parent SOA EXPIRE and retry */ }","preventionTips":["Clamp record TTLs to the parent primary zone's SOA EXPIRE before SetRecords.","If you raise a record TTL, raise the parent SOA EXPIRE first.","Note the sub-domain uses the PARENT zone's SOA EXPIRE, not its own."],"tags":["dns","primary-zone","subdomain","ttl","soa-expire","dnsserverexception"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}