{"record":{"id":"e4feb2326d401ddd","repo":"juanfont/headscale","slug":"derp-server-private-key-and-noise-private-key-are","errorCode":null,"errorMessage":"DERP server private key and noise private key are the same: %w","messagePattern":"DERP server private key and noise private key are the same: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/app.go","lineNumber":235,"sourceCode":"\t\t\t// handler to clobber /etc/resolv.conf on every tunnel-IP rebind\n\t\t\t// — the handler reapplies a Clone of lastDNSConfig and the magic\n\t\t\t// DNS routes vanish, taking the resolver with them for ~6 min\n\t\t\t// until the next route-changing netmap. Empty slice survives\n\t\t\t// Clone and carries the same \"resolve locally\" semantics\n\t\t\t// (tailscale.com/ipn/ipnlocal/node_backend.go:869 documents the\n\t\t\t// empty-resolver Routes form for Issue 2706).\n\t\t\tapp.cfg.TailcfgDNSConfig.Routes[d.WithoutTrailingDot()] = []*dnstype.Resolver{}\n\t\t}\n\t}\n\n\tif cfg.DERP.ServerEnabled {\n\t\tderpServerKey, err := readOrCreatePrivateKey(cfg.DERP.ServerPrivateKeyPath)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"reading or creating DERP server private key: %w\", err)\n\t\t}\n\n\t\tif derpServerKey.Equal(*noisePrivateKey) {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"DERP server private key and noise private key are the same: %w\",\n\t\t\t\terr,\n\t\t\t)\n\t\t}\n\n\t\tif cfg.DERP.ServerVerifyClients {\n\t\t\tt := http.DefaultTransport.(*http.Transport) //nolint:forcetypeassert\n\t\t\tt.RegisterProtocol(\n\t\t\t\tderpServer.DerpVerifyScheme,\n\t\t\t\tderpServer.NewDERPVerifyTransport(app.handleVerifyRequest),\n\t\t\t)\n\t\t}\n\n\t\tembeddedDERPServer, err := derpServer.NewDERPServer(\n\t\t\tcfg.ServerURL,\n\t\t\tkey.NodePrivate(*derpServerKey),\n\t\t\t&cfg.DERP,\n\t\t)","sourceCodeStart":217,"sourceCodeEnd":253,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/app.go#L217-L253","documentation":"Startup guard rejecting the configuration where the embedded DERP server key and the Noise protocol key are literally the same key (derpServerKey.Equal(*noisePrivateKey)). Reusing one machine key for both protocols is unsafe because key compromise or usage contexts collide. Note a code defect in the source: this fmt.Errorf wraps `err`, which is nil at this point (the read succeeded), so the rendered message ends with '%!w(<nil>)' instead of a cause — the check itself is still a hard startup failure.","triggerScenarios":"Setting derp.server_private_key_path to the same file as noise_private_key_path in the config; the DERP key file being a copy of the noise key file; both paths defaulting to one shared mounted secret.","commonSituations":"Ops configuring a single generic 'headscale key' secret mounted at both paths; copying an existing config and changing only one section; Docker/K8s setups mounting one key volume at both locations.","solutions":["Point derp.server_private_key_path at a different, non-existent path — headscale will generate a fresh DERP key on startup","Or pre-generate a second, independent key file for the DERP server","Remove any manual copy of the noise key at the DERP path and restart","Code-level: replace the nil-err wrap with a sentinel error so the message renders correctly"],"exampleFix":"# before (config.yaml)\nnoise_private_key_path: /var/lib/headscale/noise_private_key\nderp:\n  server_enabled: true\n  server_private_key_path: /var/lib/headscale/noise_private_key  # same file -> error\n\n# after\nnoise_private_key_path: /var/lib/headscale/noise_private_key\nderp:\n  server_enabled: true\n  server_private_key_path: /var/lib/headscale/derp_private_key  # auto-generated","handlingStrategy":"validation","validationCode":"if cfg.DERP.ServerPrivateKeyPath == cfg.NoisePrivateKeyPath {\n    return errors.New(\"derp.server_private_key_path must differ from noise_private_key_path\")\n}\n// stronger: compare file contents, since identical copies also trigger the guard\nif b1, e1 := os.ReadFile(cfg.DERP.ServerPrivateKeyPath); e1 == nil {\n    if b2, e2 := os.ReadFile(cfg.NoisePrivateKeyPath); e2 == nil && bytes.Equal(b1, b2) {\n        return errors.New(\"DERP key and noise key are identical; generate a separate DERP key\")\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Mount/generate a distinct secret for each key purpose in containerized deploys","Config-review the two key paths together whenever the DERP server is enabled","If the error appears, delete the DERP key file and restart — a fresh independent key is created"],"tags":["derp","noise","crypto-keys","configuration","startup"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}