{"record":{"id":"04f0b3e4d585c1af","repo":"quarkusio/quarkus","slug":"failed-to-obtain-certificate-reason","errorCode":null,"errorMessage":"Failed to obtain certificate: <reason>","messagePattern":"Failed to obtain certificate: <reason>","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"extensions/tls-registry/cli/src/main/java/io/quarkus/tls/cli/letsencrypt/LetsEncryptHelpers.java","lineNumber":193,"sourceCode":"            boolean staging,\n            String domain,\n            File certChainPemLoc,\n            File privateKeyPemLoc,\n            String acmeServerUrl,\n            String acmeStagingServerUrl) {\n        AcmeAccount acmeAccount = getAccount(letsEncryptPath, acmeServerUrl, acmeStagingServerUrl);\n\n        AUDIT.info(\"Requesting certificate - domain: \" + domain + \", staging: \" + staging + \", server: \"\n                + (acmeServerUrl != null ? acmeServerUrl : \"default\"));\n\n        X509CertificateChainAndSigningKey certChainAndPrivateKey;\n        try {\n            certChainAndPrivateKey = acmeClient.obtainCertificateChain(acmeAccount, staging, domain);\n            AUDIT.info(\"Certificate chain obtained successfully - domain: \" + domain + \", chain-length: \"\n                    + certChainAndPrivateKey.getCertificateChain().length);\n        } catch (AcmeException t) {\n            AUDIT.error(\"Failed to obtain certificate - domain: \" + domain + \", staging: \" + staging, t);\n            throw new RuntimeException(\"Failed to obtain certificate: \" + t.getMessage(), t);\n        }\n        LOGGER.info(\"\\uD83D\\uDD35 Certificate and private key issued, converting them to PEM files\");\n\n        AUDIT.info(\"Writing certificate to: \" + certChainPemLoc.getAbsolutePath());\n        AUDIT.info(\"Writing private key to: \" + privateKeyPemLoc.getAbsolutePath());\n\n        try {\n            LetsEncryptHelpers.writePrivateKeyAndCertificateChainsAsPem(certChainAndPrivateKey.getSigningKey(),\n                    certChainAndPrivateKey.getCertificateChain(), privateKeyPemLoc, certChainPemLoc);\n        } catch (Exception ex) {\n            AUDIT.error(\"Failed to write certificate files - cert: \" + certChainPemLoc + \", key: \"\n                    + privateKeyPemLoc, ex);\n            throw new RuntimeException(\"Failure to copy certificate pem: \" + ex.getMessage(), ex);\n        }\n    }\n\n    private static AcmeAccount getAccount(File letsEncryptPath, String acmeServerUrl, String acmeStagingServerUrl) {\n        LOGGER.debugf(\"Getting account from %s\", letsEncryptPath);","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/tls-registry/cli/src/main/java/io/quarkus/tls/cli/letsencrypt/LetsEncryptHelpers.java#L175-L211","documentation":"issueCertificate calls acmeClient.obtainCertificateChain to request a certificate from the ACME server. If the ACME interaction raises an AcmeException (challenge failed, invalid domain, terms not agreed, server rejection), the exception is logged and rethrown as this RuntimeException carrying the ACME reason in the message. It means the certificate was never issued.","triggerScenarios":"Calling issueCertificate or renewCertificate when the ACME server rejects or fails the order — domain validation challenge failure, DNS not pointing at the host, ACME account invalid, or the ACME server returning an error.","commonSituations":"Domain's DNS A/AAAA record not pointing to the machine running the challenge; port 80/443 blocked so the HTTP-01/TLS-ALPN challenge fails; Let's Encrypt rate limits hit; using the staging URL vs production mismatch; expired ACME account.","solutions":["Read the <reason> in the message and the logged AUDIT error for the underlying ACME failure","Verify DNS for the domain resolves to this machine and the required ports (80/443) are reachable from the internet","Check rate limits and account status at the ACME server (e.g. Let's Encrypt rate limits page)","Test against the staging ACME URL first to avoid production rate limits, then switch to production","Recreate the ACME account if the account is invalid or terms-of-service agreement changed"],"exampleFix":"// before\nacmeClient.obtainCertificateChain(acmeAccount, staging, \"typo-domain.example.com\"); // challenge fails\n// after: ensure DNS record exists and points to this host before issuing\n// dig +short mydomain.example.com  -> should return this server's IP","handlingStrategy":"retry","validationCode":"// Pre-flight checks before issuing:\n// DNS resolves to this host:\nboolean dnsOk = !InetAddress.getAllByName(domain)[0].getHostAddress().isEmpty();\n// Port 80 reachable (HTTP-01 challenge):\ntry (Socket s = new Socket()) {\n    s.connect(new InetSocketAddress(domain, 80), 5000); // throws if unreachable\n}","typeGuard":null,"tryCatchPattern":"try {\n    LetsEncryptHelpers.issueCertificate(acmeClient, letsEncryptPath, staging, domain, ...);\n} catch (RuntimeException e) {\n    if (e.getMessage() != null && e.getMessage().startsWith(\"Failed to obtain certificate:\")) {\n        AUDIT.error(\"ACME issuance failed for \" + domain + \": \" + e.getMessage() + \"; verify DNS, ports 80/443, and rate limits before retry\", e);\n        // retry only after fixing validation; consider staging endpoint first\n    } else {\n        throw e;\n    }\n}","preventionTips":["Verify DNS A/AAAA records point to the host before requesting certificates","Ensure ports 80/443 are open and publicly reachable for ACME challenges","Use the Let's Encrypt staging endpoint while testing to avoid rate limits","Schedule renewals well before expiry (e.g. 30 days) and back off on failures","Recreate/refresh the ACME account if server rejects it"],"tags":["acme","letsencrypt","certificate","network"],"backgroundTag":"acme-certificate-issue-failed","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}