{"record":{"id":"1c33b0491e0da817","repo":"quarkusio/quarkus","slug":"missing-certificate-authority-challenge","errorCode":null,"errorMessage":"Missing certificate authority challenge","messagePattern":"Missing certificate authority challenge","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"extensions/tls-registry/cli/src/main/java/io/quarkus/tls/cli/letsencrypt/AcmeClient.java","lineNumber":153,"sourceCode":"\n    }\n\n    @Override\n    public AcmeChallenge proveIdentifierControl(AcmeAccount account, List<AcmeChallenge> challenges)\n            throws AcmeException {\n        Assert.checkNotNullParam(\"account\", account);\n        Assert.checkNotNullParam(\"challenges\", challenges);\n        AcmeChallenge selectedChallenge = null;\n        for (AcmeChallenge challenge : challenges) {\n            if (challenge.getType() == AcmeChallenge.Type.HTTP_01) {\n                AUDIT.info(\"Selected HTTP-01 challenge for domain validation\");\n                LOGGER.debug(\"HTTP 01 challenge is selected\");\n                selectedChallenge = challenge;\n                break;\n            }\n        }\n        if (selectedChallenge == null) {\n            throw new RuntimeException(\"Missing certificate authority challenge\");\n        }\n\n        // ensure the token is valid before proceeding\n        String token = selectedChallenge.getToken();\n        if (!token.matches(TOKEN_REGEX)) {\n            AUDIT.error(\"Invalid challenge token format - rejecting\");\n            throw new RuntimeException(\"Invalid certificate authority challenge\");\n        }\n\n        LOGGER.debugf(\"Preparing a selected challenge content for token %s\", token);\n        String selectedChallengeString = selectedChallenge.getKeyAuthorization(account);\n\n        // Check rate limit before uploading challenge\n        checkRateLimit(\"challenge-upload\");\n\n        // respond to the http challenge\n        if (managementClient != null) {\n            //TODO: Use JsonObject once POST is supported","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/tls-registry/cli/src/main/java/io/quarkus/tls/cli/letsencrypt/AcmeClient.java#L135-L171","documentation":"During ACME identifier proofing, AcmeClient iterates the challenges offered by the certificate authority for the order and selects the HTTP-01 challenge. If none of the returned challenges is an HTTP-01 type, selectedChallenge stays null and this RuntimeException is thrown. It means the CA did not offer the challenge type this client is prepared to fulfill.","triggerScenarios":"Calling proveIdentifierControl when the ACME order's authorizations contain no http-01 challenge — e.g. the CA profile only offers dns-01 or tls-alpn-01, the identifier is a wildcard domain (wildcards require dns-01), or the authorization object structure changed so no challenge matched the selection logic.","commonSituations":"Requesting a wildcard certificate (*.example.com), which Let's Encrypt only validates via DNS-01; using an ACME directory/CA (e.g. a private Pebble or ZeroSSL profile) that does not enable HTTP-01; an order for an identifier whose authorization already moved to a state without a usable challenge.","solutions":["Verify you are not requesting a wildcard domain — switch to DNS-01 validation for wildcards or use a concrete subdomain","Check the ACME server configuration/profile to ensure the http-01 challenge type is enabled","Inspect the order's authorization challenges (log challenge types) to confirm what the CA actually offers","Point the client at the standard Let's Encrypt ACME directory if a non-standard CA was configured"],"exampleFix":"// before: wildcard requires dns-01, so no http-01 challenge exists\nacmeClient.proveIdentifierControl(\"*.example.com\", ...);\n\n// after: use a concrete hostname for http-01\nacmeClient.proveIdentifierControl(\"app.example.com\", ...);","handlingStrategy":"validation","validationCode":"import sh.acme.*;\n\nstatic boolean hasHttp01Challenge(Authorization auth) {\n    return auth != null && auth.getChallenges() != null && auth.getChallenges().stream()\n        .anyMatch(c -> ChallengeType.HTTP_01.equals(c.getType()));\n}\n// check before proving: if (!hasHttp01Challenge(order.getAuthorization(domain))) skip/switch to dns-01\n","typeGuard":"static boolean isHttp01(Challenge c) {\n    return c != null && \"http-01\".equals(c.getType());\n}\n","tryCatchPattern":"try {\n    acmeClient.proveIdentifierControl(identifier, account, ...);\n} catch (RuntimeException e) {\n    if (e.getMessage().equals(\"Missing certificate authority challenge\")) {\n        // fall back to DNS-01 or fail with a clear message about unsupported challenge type\n    } else {\n        throw e;\n    }\n}","preventionTips":["Avoid wildcard identifiers when using HTTP-01; use DNS-01 for *.domain certificates","Confirm the target ACME CA profile enables the http-01 challenge type","Log available challenge types per authorization to detect CA capability issues early","Test against a CA (Pebble/Let's Encrypt staging) known to offer http-01 before production runs"],"tags":["acme","letsencrypt","http01-challenge","challenge-validation"],"backgroundTag":"acme-challenge-unavailable","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}