TechnitiumSoftware/DnsServer · error · InvalidOperationException

Invalid SOA record.

Error message

Invalid SOA record.

What it means

Thrown as InvalidOperationException by PrimaryZone.SetRecords() in the SOA case when the input does not contain exactly one record, or that record's name does not equal the zone's apex name (_name). A zone must have exactly one SOA at its apex; any other shape is malformed input.

Source

Thrown at DnsServerCore/Dns/Zones/PrimaryZone.cs:2537

                        foreach (DnsResourceRecord record in records)
                        {
                            if (record.GetAuthGenericRecordInfo().Disabled)
                                throw new DnsServerException("Cannot set records: disabling records in a signed zones is not supported.");
                        }

                        break;
                }
            }

            switch (type)
            {
                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();

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Ensure exactly one SOA record is passed and that its Name equals the zone apex name (case-insensitive).
  2. Normalize imported SOA records by overwriting their Name with the zone apex before calling SetRecords.
  3. Validate records.Count == 1 and the name match before the call.

Example fix

// before
zone.SetRecords(DnsResourceRecordType.SOA, soaRecords); // throws if count!=1 or name mismatch

// after
var soa = soaRecords.Single();
soa = new DnsResourceRecord(zoneName, soa.Type, soa.Class, soa.OriginalTtlValue, soa.RDATA);
zone.SetRecords(DnsResourceRecordType.SOA, new[] { soa });
Defensive patterns

Strategy: validation

Validate before calling

// Validate SOA shape before SetRecords.
if (type == DnsResourceRecordType.SOA)
{
    if (records.Count != 1)
        throw new ArgumentException("SOA must have exactly one record.");
    if (!records[0].Name.Equals(zone.Name, StringComparison.OrdinalIgnoreCase))
        throw new ArgumentException("SOA owner name must equal the zone apex.");
}

zone.SetRecords(type, records);

Type guard

static bool IsValidApexSoa(IReadOnlyList<DnsResourceRecord> rs, string zoneName) =>
    rs.Count == 1 && rs[0].Name.Equals(zoneName, StringComparison.OrdinalIgnoreCase);

Try / catch

try { zone.SetRecords(type, records); }
catch (InvalidOperationException ex) when (ex.Message == "Invalid SOA record.")
{ Log.Error("SOA must be a single record whose name equals the zone apex."); }

Prevention

When it happens

Trigger: Calling SetRecords(DnsResourceRecordType.SOA, records) where records.Count != 1, or where records[0].Name does not case-insensitively equal the zone apex name.

Common situations: Passing an empty or multi-element SOA list; passing an SOA record whose owner name was rewritten (e.g. to a subdomain) during import; constructing SOA records programmatically without setting the owner name to the zone name.

Related errors


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