{"record":{"id":"a2467e014c0c967e","repo":"juanfont/headscale","slug":"configuring-tls-settings-w","errorCode":null,"errorMessage":"configuring TLS settings: %w","messagePattern":"configuring TLS settings: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"hscontrol/app.go","lineNumber":680,"sourceCode":"\tsocketHandler := http.NewServeMux()\n\tsocketHandler.Handle(\"/api/v2/\", apiv2.WithLocalTrust(humaV2Mux))\n\tsocketHandler.Handle(\"/\", apiv1.WithLocalTrust(humaMux))\n\n\tsocketServer := &http.Server{\n\t\tHandler:     socketHandler,\n\t\tReadTimeout: types.HTTPTimeout,\n\t}\n\n\terrorGroup.Go(func() error { return socketServer.Serve(socketListener) })\n\n\t//\n\t//\n\t// Set up REMOTE listeners\n\t//\n\n\ttlsConfig, err := h.getTLSSettings()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"configuring TLS settings: %w\", err)\n\t}\n\n\t//\n\t//\n\t// HTTP setup\n\t//\n\t// This is the regular router that we expose\n\t// over our main Addr\n\trouter := h.createRouter(humaMux, humaV2Mux)\n\n\thttpServer := &http.Server{\n\t\tAddr:        h.cfg.Addr,\n\t\tHandler:     router,\n\t\tReadTimeout: types.HTTPTimeout,\n\n\t\t// Long polling should not have any timeout, this is overridden\n\t\t// further down the chain\n\t\tWriteTimeout: types.HTTPTimeout,","sourceCodeStart":662,"sourceCodeEnd":698,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/app.go#L662-L698","documentation":"Wraps failures of Headscale.getTLSSettings() (hscontrol/app.go:879) during startup. That function either builds an autocert (Let's Encrypt) config or loads a static certificate pair with tls.LoadX509KeyPair(h.cfg.TLS.CertPath, h.cfg.TLS.KeyPath). The error is the raw failure: an unsupported ACME challenge type (errUnsupportedLetsEncryptChallengeType) or a cert/key file that cannot be read or parsed.","triggerScenarios":"tls_cert_path/tls_key_path point to missing or unreadable files; the cert and key do not match (tls: private key does not match public key); the key file is malformed (tls: failed to find any PEM data); acme_challenge_type is set to anything other than HTTP-01 or TLS-ALPN-01 while letsencrypt.hostname is set.","commonSituations":"Renewed certificates deployed to a different path than configured; pasting a fullchain vs privkey in the wrong config keys (common with Let's Encrypt /certbot/letsencrypt/live paths); typos in the paths; PEM files with Windows line endings or extra text around the blocks; upgrading config and losing the acme_challenge_type setting so it no longer matches a valid value.","solutions":["Verify both files exist and are readable by the headscale user: ls -l <tls_cert_path> <tls_key_path>.","Validate the pair matches and parses: openssl x509 -in cert.pem -noout -modulus | openssl md5 and openssl rsa -in key.pem -noout -modulus | openssl md5 (hashes must be equal).","Fix acme_challenge_type: it must be exactly HTTP-01 or TLS-ALPN-01 when letsencrypt.hostname is set.","Check the PEM files are unmodified (no appended logs/text) and use Unix line endings; re-copy from the source if in doubt."],"exampleFix":"# before (config.yaml)\ntls_cert_path: /etc/headscale/tls/fullchain.pem\ntls_key_path: /etc/headscale/tls/privkey.pem\n# cert renewed via certbot to /etc/letsencrypt/live/... -> files missing\n\n# after\ntls_cert_path: /etc/letsencrypt/live/example.org/fullchain.pem\ntls_key_path: /etc/letsencrypt/live/example.org/privkey.pem","handlingStrategy":"validation","validationCode":"// Pre-flight TLS pair check before handing config to Headscale.\nfunc tlsPairLoadable(certPath, keyPath string) error {\n    _, err := tls.LoadX509KeyPair(certPath, keyPath)\n    return err\n}\n\n// And for ACME:\nfunc challengeTypeValid(t string) bool {\n    return t == \"HTTP-01\" || t == \"TLS-ALPN-01\"\n}","typeGuard":null,"tryCatchPattern":"if err := h.Serve(); err != nil {\n    if errors.Is(err, errUnsupportedLetsEncryptChallengeType) {\n        // fix acme_challenge_type in config\n    } else if _, ok := err.(*tls.CertificateVerificationError); ok || strings.Contains(err.Error(), \"PEM\") {\n        // fix cert/key files\n    }\n}","preventionTips":["Validate cert/key pairs in CI for any config change (openssl x509/rsa modulus check).","Automate cert renewal with a deploy hook that reloads headscale.","Pin acme_challenge_type explicitly to HTTP-01 or TLS-ALPN-01 when using Let's Encrypt."],"tags":["tls","certificates","startup","configuration","lets-encrypt"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}