Billionmail/BillionMail · error

Failed to apply for SSL certificate: {}

Error message

Failed to apply for SSL certificate: {}

What it means

The final ACME step calls client.Certificate.Obtain(request) to run the chosen challenge and fetch the certificate. This error wraps every failure lego reports during that flow — challenge validation failures (HTTP-01 on 127.0.0.1:60880 unreachable by the CA, or DNS-01 TXT record wrong/propagating), authorization failures, and CA-side rate limits. The wrapped err text names the exact failing domain and challenge type.

Source

Thrown at core/internal/service/acme/acme.go:465

		reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
		if err != nil {
			return "", "", errors.New(public.LangCtx(ctx, "Failed to register user: {}", err.Error()))
		}
	}

	// Save user information
	myUser.Registration = reg

	// Submit application
	request := certificate.ObtainRequest{
		Domains: domains,
		Bundle:  true,
	}

	// Get certificate
	certificates, err := client.Certificate.Obtain(request)
	if err != nil {
		return "", "", errors.New(public.LangCtx(ctx, "Failed to apply for SSL certificate: {}", err.Error()))
	}

	// Save certificate files if path is provided
	if savePath != "" {
		// Create directory if it doesn't exist
		if !public.FileExists(savePath) {
			err = os.MkdirAll(savePath, 0750)
			if err != nil {
				return "", "", errors.New(public.LangCtx(ctx, "Failed to create directory: {}", err.Error()))
			}
		}

		// Save certificate and private key files
		certificateFile := filepath.Join(savePath, "certificate.pem")
		privateKeyFile := filepath.Join(savePath, "private_key.pem")

		_, err = public.WriteFile(certificateFile, string(certificates.Certificate))
		if err != nil {

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Read the wrapped err to identify the failing domain/challenge; for http-01 verify the CA can reach the domain on port 80 and that traffic reaches the challenge server on 127.0.0.1:60880 (open/forward port 80, disable blocking proxy rules)
  2. For dns-01, confirm the DNS provider token works and the TXT record appears (_acme-challenge.<domain>) via dig before retrying
  3. Wait out rate limits or use the Let's Encrypt staging CA while debugging, then retry with production

Example fix

// before
// port 80 closed; CA cannot reach challenge
// after
// open port 80 in firewall/NAT so http://<domain>/.well-known/acme-challenge/ reaches 127.0.0.1:60880
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight: port 80 reachable and DNS points here (http-01)
if vtype == "http" {
    ip, _ := net.LookupIP(domain)
    pub, _ := externalIP()
    if len(ip) == 0 || ip[0].String() != pub { return errors.New("domain does not resolve to this server") }
}
// dns-01: check TXT after provider setup
// dig TXT _acme-challenge.<domain> +short

Type guard

func challengeReachable(vtype, domain string) bool {
    if vtype == "http" {
        conn, err := net.DialTimeout("tcp", domain+":80", 5*time.Second)
        if err != nil { return false }
        conn.Close()
    }
    return true
}

Try / catch

cert, _, err := ApplySSLWithExistingServer(ctx, ...)
if err != nil && strings.Contains(err.Error(), "Failed to apply for SSL certificate") {
    // challenge failures are often transient (propagation, rate limits)
    time.Sleep(1 * time.Minute)
    return retryApplyWithBackoff(ctx, 3)
}

Prevention

When it happens

Trigger: ApplySSLWithExistingServer (via Apply, StartRenew, ApplyLetsEncryptCertWithHttp, ApplyConsoleCert) where the CA cannot validate the challenge: for http-01, the CA cannot reach http://<domain>/.well-known/acme-challenge (port 80 closed or not forwarded to 127.0.0.1:60880); for dns-01, the TXT record was not created/propagated or credentials lack zone write access.

Common situations: Firewall/NAT blocking port 80 in front of the challenge server; domain's DNS pointing elsewhere than the server running this code; DNS propagation delay; Let's Encrypt rate limits after repeated failures; expired wildcard attempts using dns provider with wrong zone.

Understand the failure class

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/9b16252bf1959b98. Report an issue: GitHub.