{"record":{"id":"98dee07ee04ae3cc","repo":"AdguardTeam/AdGuardHome","slug":"parsing-tls-certificate-w","errorCode":null,"errorMessage":"parsing tls certificate: %w","messagePattern":"parsing tls certificate: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/aghtls/defaultmanager.go","lineNumber":163,"sourceCode":"\t\tmgr.extTLSConf.Enabled = false\n\n\t\t// Don't wrap the error, because it's informative enough as is.\n\t\treturn err\n\t}\n\n\tcert, err := tls.X509KeyPair(mgr.extTLSConf.CertificateChainData, mgr.extTLSConf.PrivateKeyData)\n\tif err != nil {\n\t\t// DNSCrypt provides its own certificate, meaning we can ignore TLS\n\t\t// certificate parsing errors.\n\t\tif mgr.extTLSConf.PortDNSCrypt != 0 && mgr.extTLSConf.DNSCryptConfigFile != \"\" {\n\t\t\tmgr.logger.InfoContext(ctx, \"dnscrypt is configured\")\n\n\t\t\treturn nil\n\t\t}\n\n\t\tmgr.extTLSConf.Enabled = false\n\n\t\treturn fmt.Errorf(\"parsing tls certificate: %w\", err)\n\t}\n\n\tslices.Sort(cert.Leaf.DNSNames)\n\n\tmgr.tlsCert = &cert\n\tmgr.setCertFileTime(ctx)\n\tmgr.tlsConf = &tls.Config{\n\t\tRootCAs:        mgr.rootCerts,\n\t\tCipherSuites:   mgr.customCipherIDs,\n\t\tGetCertificate: mgr.onGetCertificate,\n\t\tMinVersion:     tls.VersionTLS12,\n\t}\n\n\treturn nil\n}\n\n// type check\nvar _ Manager = (*DefaultManager)(nil)","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/AdguardTeam/AdGuardHome/blob/b41aefbe51c8dde65e2c50f093996afa0502edf9/internal/aghtls/defaultmanager.go#L145-L181","documentation":"prepareTLSConfig (used by NewDefaultManager) loads and parses the configured TLS certificate with tls.X509KeyPair-style parsing; failure here means the certificate or key material couldn't be parsed — malformed PEM, missing blocks, or key/cert mismatch. External TLS is disabled as a side effect (mgr.extTLSConf.Enabled = false).","triggerScenarios":"NewDefaultManager with a certificate file or key that isn't valid PEM, where the key doesn't match the certificate, where either file is empty/truncated, or where the key is encrypted and can't be decoded; also file read issues upstream surface wrapped here.","commonSituations":"Expired-then-regenerated certs pasted with truncated headers; cert and key from different issuance rounds; wrong file paths yielding empty content; keys in PKCS#12 or unusual formats instead of PEM.","solutions":["Validate the pair offline: openssl x509 -in cert.pem -noout and openssl rsa -in key.pem -check","Confirm cert and key match: compare openssl x509 -noout -modulus | openssl md5 for both (or pkey for RSA alternatives)","Re-export/re-issue the pair as unencrypted PEM and update the config paths","After fixing, restart so NewDefaultManager re-attempts loading"],"exampleFix":"# verify match\nopenssl x509 -in cert.pem -noout -modulus | openssl md5\nopenssl rsa -in key.pem -noout -modulus | openssl md5","handlingStrategy":"validation","validationCode":"// verify pair parses and matches before starting the manager:\nif _, err := tls.X509KeyPair(certPEM, keyPEM); err != nil { return fmt.Errorf(\"invalid pair: %w\", err) }","typeGuard":"func certMatchesKey(certPEM, keyPEM []byte) bool { _, err := tls.X509KeyPair(certPEM, keyPEM); return err == nil }","tryCatchPattern":"mgr, err := aghtls.NewDefaultManager(...)\nif err != nil && strings.Contains(err.Error(), \"parsing tls certificate\") {\n    // fail startup, keep HTTP-only, alert operator; external TLS is disabled by this point\n}","preventionTips":["Validate cert/key pairs at deploy time with openssl","Automate renewal so hand-pasted PEM truncation can't happen","Monitor certificate expiry and reload health after rotation","Never mix cert/key from different issuance batches"],"tags":["tls","x509","certificate","pem","config"],"backgroundTag":"tls-certificate-parse-failed","analyzedSha":"b41aefbe51c8dde65e2c50f093996afa0502edf9","analyzedAt":"2026-08-27T04:57:55.097Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}