{"record":{"id":"f765490d2e90e4ed","repo":"TechnitiumSoftware/DnsServer","slug":"cannot-sign-zone-the-zone-is-already-signed","errorCode":null,"errorMessage":"Cannot sign zone: the zone is already signed.","messagePattern":"Cannot sign zone: the zone is already signed\\.","errorType":"exception","errorClass":"DnsServerException","httpStatus":null,"severity":"error","filePath":"DnsServerCore/Dns/Zones/PrimaryZone.cs","lineNumber":478,"sourceCode":"                if (saltLength > 0)\n                {\n                    salt = new byte[saltLength];\n                    RandomNumberGenerator.Fill(salt);\n                }\n                else\n                {\n                    salt = [];\n                }\n            }\n\n            SignZone([kskPrivateKey, zskPrivateKey], dnsKeyTtl, useNSec3, iterations, salt);\n        }\n\n        public void SignZone(IReadOnlyCollection<DnssecPrivateKey> dnssecPrivateKeys, uint dnsKeyTtl, bool useNSec3, ushort iterations = 0, byte[] salt = null)\n        {\n            //do validations\n            if (_dnssecStatus != AuthZoneDnssecStatus.Unsigned)\n                throw new DnsServerException(\"Cannot sign zone: the zone is already signed.\");\n\n            if (useNSec3)\n            {\n                if (iterations > 50)\n                    throw new ArgumentOutOfRangeException(nameof(iterations), \"NSEC3 iterations valid range is 0-50\");\n\n                if (salt.Length > 32)\n                    throw new ArgumentOutOfRangeException(nameof(salt), \"NSEC3 salt length valid range is 0-32\");\n            }\n\n            bool foundKsk = false;\n            bool foundZsk = false;\n\n            foreach (DnssecPrivateKey dnssecPrivateKey in dnssecPrivateKeys)\n            {\n                switch (dnssecPrivateKey.KeyType)\n                {\n                    case DnssecPrivateKeyType.KeySigningKey:","sourceCodeStart":460,"sourceCodeEnd":496,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/DnsServerCore/Dns/Zones/PrimaryZone.cs#L460-L496","documentation":"Thrown by the collection overload PrimaryZone.SignZone(...) when _dnssecStatus != AuthZoneDnssecStatus.Unsigned. A zone can only be signed from the Unsigned state; re-signing a zone that is already NSEC/NSEC3-signed must go through re-sign/key-roll paths. The guard raises DnsServerException as the very first validation.","triggerScenarios":"Calling zone.SignZone(...) when the zone is already SignedWithNSEC or SignedWithNSEC3 — e.g., signing twice, or signing after a failed sign that left partial state under a race.","commonSituations":"Retry loop that re-invokes SignZone after a transient failure; UI 'Sign' button pressed twice; concurrent sign requests.","solutions":["Check zone.DnssecStatus == Unsigned before calling SignZone.","If already signed and you want different params, call UnsignZone() first, then SignZone.","Serialize sign/unsign operations behind a lock to avoid concurrent double-sign."],"exampleFix":"// before\nzone.SignZone(keys, ttl, useNSec3: true);\n\n// after\nif (zone.DnssecStatus != AuthZoneDnssecStatus.Unsigned)\n    zone.UnsignZone();\nzone.SignZone(keys, ttl, useNSec3: true);","handlingStrategy":"try-catch","validationCode":"if (zone.DnssecStatus != AuthZoneDnssecStatus.Unsigned)\n    zone.UnsignZone();\nzone.SignZone(keys, ttl, useNSec3);","typeGuard":"static bool CanSign(AuthZone z) => z.DnssecStatus == AuthZoneDnssecStatus.Unsigned;","tryCatchPattern":"try { zone.SignZone(keys, ttl, useNSec3); }\ncatch (DnsServerException ex) when (ex.Message.Contains(\"already signed\"))\n{ /* concurrent signer won: refresh status, no-op or unsign+retry per intent */ }","preventionTips":["Check DnssecStatus == Unsigned before signing.","Serialize sign/unsign behind a lock to avoid races.","To change sign params, UnsignZone then SignZone."],"tags":["csharp","dns","dnssec","zone-management","concurrency","technitium-dns"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}