TechnitiumSoftware/DnsServer · error · DnsServerException
Cannot set record: SOA REFRESH cannot be greater than SOA EX
Error message
Cannot set record: SOA REFRESH cannot be greater than SOA EXPIRE.
What it means
Thrown by PrimaryZone.SetRecords() in the SOA case when newSoa.Refresh > newSoa.Expire. REFRESH is how often a secondary polls the primary; EXPIRE is how long secondary data remains valid without a successful refresh. If REFRESH exceeds EXPIRE, a zone could expire before the next scheduled refresh, so the server enforces REFRESH <= EXPIRE.
Source
Thrown at DnsServerCore/Dns/Zones/PrimaryZone.cs:2549
case DnsResourceRecordType.CNAME:
case DnsResourceRecordType.DS:
throw new InvalidOperationException("Cannot set " + type.ToString() + " record at zone apex.");
case DnsResourceRecordType.SOA:
if ((records.Count != 1) || !records[0].Name.Equals(_name, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("Invalid SOA record.");
DnsResourceRecord newSoaRecord = records[0];
DnsSOARecordData newSoa = newSoaRecord.RDATA as DnsSOARecordData;
if (newSoaRecord.OriginalTtlValue > newSoa.Expire)
throw new DnsServerException("Cannot set record: TTL cannot be greater than SOA EXPIRE.");
if (newSoa.Retry > newSoa.Refresh)
throw new DnsServerException("Cannot set record: SOA RETRY cannot be greater than SOA REFRESH.");
if (newSoa.Refresh > newSoa.Expire)
throw new DnsServerException("Cannot set record: SOA REFRESH cannot be greater than SOA EXPIRE.");
//remove any record info except serial date scheme and comments
bool useSoaSerialDateScheme;
string comments;
{
SOARecordInfo recordInfo = newSoaRecord.GetAuthSOARecordInfo();
useSoaSerialDateScheme = recordInfo.UseSoaSerialDateScheme;
comments = recordInfo.Comments;
}
newSoaRecord.Tag = null; //remove old record info
{
SOARecordInfo recordInfo = newSoaRecord.GetAuthSOARecordInfo();
recordInfo.UseSoaSerialDateScheme = useSoaSerialDateScheme;
recordInfo.Comments = comments;View on GitHub (pinned to d0484b6c1e)
Solutions
- Set REFRESH <= EXPIRE on the SOA before calling SetRecords.
- If you need a longer REFRESH, raise EXPIRE to at least that value first.
- Validate Refresh <= Expire when constructing the SOA.
Example fix
// before
var soa = new DnsSOARecordData { Refresh = 86400, Expire = 3600 };
zone.SetRecords(DnsResourceRecordType.SOA, new[] { BuildSoaRecord(ttl, soa) }); // throws
// after
var soa = new DnsSOARecordData { Refresh = 3600, Expire = 86400 };
zone.SetRecords(DnsResourceRecordType.SOA, new[] { BuildSoaRecord(ttl, soa) }); Defensive patterns
Strategy: validation
Validate before calling
if (type == DnsResourceRecordType.SOA)
{
var soa = (DnsSOARecordData)records[0].RDATA;
if (soa.Refresh > soa.Expire)
throw new ArgumentException("SOA REFRESH must be <= SOA EXPIRE.");
}
zone.SetRecords(type, records); Type guard
static bool SoaRefreshWithinExpire(DnsResourceRecord soaRecord)
{
var soa = (DnsSOARecordData)soaRecord.RDATA;
return soa.Refresh <= soa.Expire;
} Try / catch
try { zone.SetRecords(type, records); }
catch (DnsServerException ex) when (ex.Message.Contains("SOA REFRESH cannot be greater than SOA EXPIRE"))
{ Log.Error("Set SOA REFRESH <= SOA EXPIRE."); } Prevention
- Keep REFRESH <= EXPIRE in SOA templates.
- When lowering EXPIRE, re-check REFRESH.
- Validate the full REFRESH/RETRY/EXPIRE/TTL ordering before writing SOA.
When it happens
Trigger: Calling SetRecords with an SOA whose Refresh is greater than its Expire (e.g. Refresh 86400, Expire 3600).
Common situations: Aggressively lowering EXPIRE for fast staleness without lowering REFRESH; template SOA timers tuned independently and inconsistently; importing a zone whose EXPIRE was edited down.
Related errors
- Cannot set record: TTL cannot be greater than SOA EXPIRE.
- Cannot set record: SOA RETRY cannot be greater than SOA REFR
- Cannot set records: TTL cannot be greater than SOA EXPIRE.
- Invalid SOA record.
- Zone does not contain SOA record.
AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13).
Data as JSON: /api/errors/4cedbc3ccf99ead5.
Report an issue: GitHub.