{"record":{"id":"d3489a53b025a529","repo":"TechnitiumSoftware/DnsServer","slug":"cannot-update-nsec3-parameters-the-zone-must-be-s","errorCode":null,"errorMessage":"Cannot update NSEC3 parameters: the zone must be signed with NSEC3 first.","messagePattern":"Cannot update NSEC3 parameters: the zone must be signed with NSEC3 first\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/PrimaryZone.cs","lineNumber":750,"sourceCode":"                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();\n        }\n\n        public void UpdateNSec3Parameters(ushort iterations, byte saltLength)\n        {\n            if (_dnssecStatus != AuthZoneDnssecStatus.SignedWithNSEC3)\n                throw new DnsServerException(\"Cannot update NSEC3 parameters: the zone must be signed with NSEC3 first.\");\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                DisableNSec3(zones);\n\n                //since zones were removed when disabling NSEC3; get updated non empty zones list\n                List<AuthZone> nonEmptyZones = new List<AuthZone>(zones.Count);\n\n                foreach (AuthZone zone in zones)\n                {","sourceCodeStart":732,"sourceCodeEnd":768,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/PrimaryZone.cs#L732-L768","documentation":"Thrown by PrimaryZone.UpdateNSec3Parameters when the zone's DNSSEC status is not SignedWithNSEC3. Updating NSEC3 parameters only makes sense once an NSEC3 chain exists; calling it on an Unsigned zone or an NSEC-signed zone is rejected because there is no NSEC3PARAM record to rewrite.","triggerScenarios":"Calling UpdateNSec3Parameters on a zone whose DnssecStatus is Unsigned or SignedWithNSEC.","commonSituations":"A maintenance script re-applies the same NSEC3 settings to all signed zones without checking which variant each one uses; an NSEC-signed zone hits this path.","solutions":["If the zone is Unsigned, sign it first (optionally with NSEC3) via SignZone before adjusting parameters.","If the zone is SignedWithNSEC, call ConvertToNSec3 to establish NSEC3, then use UpdateNSec3Parameters for future changes.","Guard the call by checking zone.DnssecStatus == SignedWithNSEC3."],"exampleFix":"// before\nzone.UpdateNSec3Parameters(iterations: 1, saltLength: 8);\n\n// after\nif (zone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC3)\n    zone.UpdateNSec3Parameters(iterations: 1, saltLength: 8);\nelse if (zone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC)\n    zone.ConvertToNSec3(iterations: 1, saltLength: 8);","handlingStrategy":"validation","validationCode":"// Only update NSEC3 parameters on an NSEC3-signed zone\nif (zone.DnssecStatus != AuthZoneDnssecStatus.SignedWithNSEC3)\n{\n    if (zone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC)\n        zone.ConvertToNSec3(iterations, saltLength);\n    else\n        zone.SignZone(keys, dnsKeyTtl, useNSec3: true, iterations, salt);\n    return;\n}\nzone.UpdateNSec3Parameters(iterations, saltLength);","typeGuard":"static bool CanUpdateNSec3Parameters(ApexZone zone) =>\n    zone.DnssecStatus == AuthZoneDnssecStatus.SignedWithNSEC3;","tryCatchPattern":"try\n{\n    zone.UpdateNSec3Parameters(iterations, saltLength);\n}\ncatch (DnsServerException ex) when (ex.Message.Contains(\"must be signed with NSEC3 first\"))\n{\n    // convert or sign with NSEC3 first, then retry\n}","preventionTips":["Distinguish update (existing NSEC3) from convert (NSEC -> NSEC3) by checking DnssecStatus.","Centralize DNSSEC-status checks in a helper used by every NSEC3 entry point."],"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"}