{"record":{"id":"97d85058b53990b5","repo":"TechnitiumSoftware/DnsServer","slug":"cannot-convert-to-nsec3-the-zone-must-be-signed-w","errorCode":null,"errorMessage":"Cannot convert to NSEC3: the zone must be signed with NSEC for conversion.","messagePattern":"Cannot convert to NSEC3: the zone must be signed with NSEC for conversion\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/PrimaryZone.cs","lineNumber":726,"sourceCode":"\n                foreach (AuthZone zone in zones)\n                {\n                    if (!zone.IsEmpty)\n                        nonEmptyZones.Add(zone);\n                }\n\n                EnableNSec(nonEmptyZones);\n\n                _dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC;\n            }\n\n            TriggerNotify();\n        }\n\n        public void ConvertToNSec3(ushort iterations, byte saltLength)\n        {\n            if (_dnssecStatus != AuthZoneDnssecStatus.SignedWithNSEC)\n                throw new DnsServerException(\"Cannot convert to NSEC3: the zone must be signed with NSEC for conversion.\");\n\n            if (iterations > 50)\n                throw new ArgumentOutOfRangeException(nameof(iterations), \"NSEC3 iterations valid range is 0-50\");\n\n            if (saltLength > 32)\n                throw new ArgumentOutOfRangeException(nameof(saltLength), \"NSEC3 salt length valid range is 0-32\");\n\n            lock (_dnssecUpdateLock)\n            {\n                IReadOnlyList<AuthZone> zones = _dnsServer.AuthZoneManager.GetApexZoneWithSubDomainZones(_name);\n\n                DisableNSec(zones);\n                EnableNSec3(zones, iterations, saltLength);\n\n                _dnssecStatus = AuthZoneDnssecStatus.SignedWithNSEC3;\n            }\n\n            TriggerNotify();","sourceCodeStart":708,"sourceCodeEnd":744,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/PrimaryZone.cs#L708-L744","documentation":"Thrown by PrimaryZone.ConvertToNSec3 when the zone's DNSSEC status is not SignedWithNSEC. The library only permits converting a zone that is currently signed with plain NSEC into the NSEC3 variant; going straight from Unsigned to NSEC3 (or re-converting a zone already on NSEC3) is rejected so the existing NSEC chain can be cleanly disabled first.","triggerScenarios":"Calling ConvertToNSec3(iterations, saltLength) on a zone whose DnssecStatus is Unsigned, or calling it again on a zone whose DnssecStatus is already SignedWithNSEC3.","commonSituations":"A developer enables DNSSEC signing and assumes NSEC3 is the default, calling ConvertToNSec3 without first calling SignZone with useNSec3=false. Or scripting a 're-sign as NSEC3' routine that fires against an already-NSEC3 zone after a reload.","solutions":["If the zone is Unsigned, sign it first with NSEC (call SignZone with useNSec3=false), then call ConvertToNSec3.","If the zone is already SignedWithNSEC3 and you want to change salt/iterations, call UpdateNSec3Parameters instead of ConvertToNSec3.","Guard the call with a check on the zone's DnssecStatus property before invoking ConvertToNSec3."],"exampleFix":"// before\nzone.ConvertToNSec3(iterations: 2, saltLength: 8);\n\n// after\nif (zone.DnssecStatus == AuthZoneDnssecStatus.Unsigned)\n    zone.SignZone(keys, dnsKeyTtl, useNSec3: false);\nif (zone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC)\n    zone.ConvertToNSec3(iterations: 2, saltLength: 8);\nelse if (zone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC3)\n    zone.UpdateNSec3Parameters(iterations: 2, saltLength: 8);","handlingStrategy":"validation","validationCode":"// Verify the zone is on NSEC before converting to NSEC3\nif (zone.DnssecStatus != AuthZoneDnssecStatus.SignedWithNSEC)\n{\n    if (zone.DnssecStatus == AuthZoneDnssecStatus.Unsigned)\n        zone.SignZone(keys, dnsKeyTtl, useNSec3: false);\n    else if (zone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC3)\n    {\n        zone.UpdateNSec3Parameters(iterations, saltLength);\n        return;\n    }\n}\nzone.ConvertToNSec3(iterations, saltLength);","typeGuard":"static bool CanConvertToNSec3(ApexZone zone) =>\n    zone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC;","tryCatchPattern":"try\n{\n    zone.ConvertToNSec3(iterations, saltLength);\n}\ncatch (DnsServerException ex) when (ex.Message.Contains(\"must be signed with NSEC for conversion\"))\n{\n    // sign with NSEC first, then retry; or route to UpdateNSec3Parameters\n}","preventionTips":["Always read zone.DnssecStatus before invoking a DNSSEC state-transition method.","Treat NSEC->NSEC3 as a one-way conversion; use UpdateNSec3Parameters to adjust an existing NSEC3 zone."],"tags":["dnssec","nsec3","state-precondition","validation","csharp"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}