coredns/coredns · error
no SOA RRSIG found in first 100 records
Error message
no SOA RRSIG found in first 100 records
What it means
resign() scans the first 100 records of a signed zone looking for the RRSIG covering the SOA record. If none is found within that limit it gives up and returns this error, because the plugin cannot determine signature freshness for the zone's SOA.
Source
Thrown at plugin/sign/signer.go:160
if now.Sub(incep) >= 0 && now.Sub(incep) > durationResignDays {
return fmt.Errorf("inception %q was more than: %s ago from %s: %s", incep.Format(timeFmt), durationResignDays, now.Format(timeFmt), now.Sub(incep))
}
// Inception hasn't even start yet.
if now.Sub(incep) < 0 {
return fmt.Errorf("inception %q date is in the future: %s", incep.Format(timeFmt), now.Sub(incep))
}
expire, _ := time.Parse("20060102150405", dns.TimeToString(x.Expiration))
if expire.Sub(now) < durationExpireDays {
return fmt.Errorf("expiration %q is less than: %s away from %s: %s", expire.Format(timeFmt), durationExpireDays, now.Format(timeFmt), expire.Sub(now))
}
}
i++
if i > 100 {
// 100 is a random number. A SOA record should be the first in the zonefile, but RFC 1035 doesn't actually mandate this. So it could
// be 3rd or even later. The number 100 looks crazy high enough that it will catch all weird zones, but not high enough to keep the CPU
// busy with parsing all the time.
return fmt.Errorf("no SOA RRSIG found in first 100 records")
}
}
return zp.Err()
}
func signAndLog(s *Signer, why error) {
now := time.Now().UTC()
z, err := s.Sign(now)
log.Infof("Signing %q because %s", s.origin, why)
if err != nil {
log.Warningf("Error signing %q with key tags %q in %s: %s, next: %s", s.origin, keyTag(s.keys), time.Since(now), err, now.Add(durationRefreshHours).Format(timeFmt))
return
}
if err := s.write(z); err != nil {
log.Warningf("Error signing %q: failed to move zone file into place: %s", s.origin, err)
returnView on GitHub (pinned to 558c9757a9)
Solutions
- Re-sign the zone so an SOA RRSIG exists (or sign it at all)
- Move the SOA record and its RRSIG to the top of the zone file (RFC 1035 convention)
- Validate the zone file with named-checkzone or dnssec-verify
- Ensure the correct zone file is loaded
Example fix
// before (SOA RRSIG missing) example.org. 3600 IN SOA ns1 ... example.org. 3600 IN NS ns1 ... // after: re-sign so the SOA carries an RRSIG example.org. 3600 IN RRSIG SOA 8 2 3600 ... (sig) example.org. 3600 IN SOA ns1 ...
Defensive patterns
Strategy: validation
Validate before calling
func hasSOARRSIG(z *zone.File) bool {
for _, rr := range z.RR {
if r, ok := rr.(*dns.RRSIG); ok && r.TypeCovered == dns.TypeSOA {
return true
}
}
return false
} Try / catch
if err := resign(zoneFile); err != nil {
if strings.Contains(err.Error(), "no SOA RRSIG") {
log.Printf("zone not signed or malformed: %v", err)
// run full sign instead of resign check
}
} Prevention
- Always place the SOA record (with its RRSIG) at the top of the zone file
- Run dnssec-verify on zone files before serving
- Never hand-edit or strip RRSIG records from signed zones
- Validate zones with named-checkzone as part of CI
When it happens
Trigger: resign() is called on a zone whose first 100 records contain no RRSIG with TypeCovered == dns.TypeSOA (no SOA RRSIG at all, or SOA pushed beyond the 100th record).
Common situations: Serving an unsigned zone file through the sign plugin's resign check path; a malformed/unsorted zone file with the SOA buried past 100 records; hand-edited zone missing its SOA RRSIG.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- file %q has no SOA record
- key %s (keyid: %d) can not sign any of the zones
- file %q has no SOA record for origin %s
- NSEC3 zone is not supported, dropping RR: %s for zone: %s
- no SOA
AI-assisted analysis of coredns/coredns@558c9757a9 (2026-09-06).
Data as JSON: /api/errors/2510424a3d304000.
Report an issue: GitHub.