1---
2title: Troubleshooting
3description: Diagnose failed DNS challenges, rejected orders, rate limits, and runtime certificate operations.
4---
5
6Start with the error tag and the affected identifier. Verify your DNS path with
7Let's Encrypt staging before repeatedly placing production orders. Preserve
8sanitized error metadata, but keep account keys, certificate keys, EAB HMAC
9values, and authorization headers out of logs.
10
11## Missing DNS solver
12
13`ACME.DnsSolverNotRegistered` means the descriptor's `type` has no registered
14factory in the stack's provider context. For Cloudflare, register both providers:
15
16```typescript
17const providers = Layer.mergeAll(ACME.providers(), Cloudflare.providers());
18```
19
20Pass this layer as the stack's `providers`. For runtime issuance, use
21`acmeDnsSolver(dns)` with a bound DNS client instead of passing the deploy-time
22descriptor returned by `AcmeSolver`.
23
24## DNS API access fails
25
26`ACME.DnsSolverError` wraps a failure publishing or cleaning up a challenge.
27Check the zone ID, credential scope, and whether the DNS name belongs to that
28zone. An API credential valid for one Cloudflare account is not necessarily
29valid for another account's zone.
30
31For runtime `WriteDnsHttp`, the deployment identity must also be allowed to
32create the scoped token. A failed cleanup is operationally significant even
33if the CA validated the name; inspect the TXT records and retry cleanup safely.
34
35## TXT propagation times out
36
37`ACME.DnsPropagationTimeout` identifies the challenge name that was not observed
38within the configured budget. Check authoritative delegation, the zone ID, the
39exact TXT value, and whether there is an existing CNAME at that name. Public
40recursive DNS may still have a negative or previous-value cache entry.
41
42The deploy-time Cloudflare timeout defaults to 90 seconds and includes the
4360-second post-visibility delay. A timeout shorter than the delay cannot work.
44Lookup attempts are also bounded; increasing `timeout` alone does not remove
45that cap. See [DNS validation](/acme/dns-validation#adjust-the-propagation-budget).
46
47## The CA rejects a challenge
48
49`ACME.ChallengeFailed` carries the identifier, CA problem type, and detail.
50`ACME.OrderInvalid` carries the order URL and reported problems. A successful
51DNS API write does not prove that every CA validator sees the record.
52
53For an "incorrect TXT record" failure, inspect stale records and cached values
54before shortening propagation delays or retrying. Check CAA policy if the CA
55reports that it is not authorized to issue. Wildcards require DNS-01 support;
56`ACME.ChallengeUnsupported` reports the challenge types the CA offered instead.
57
58## An order times out
59
60`ACME.OrderTimeout` means polling did not reach a terminal state within the
61implementation's bounded polling budget. Keep the order URL and status for
62investigation. The CA may finish processing later; immediately starting another
63order can consume additional issuance allowance without resolving the original
64problem.
65
66## Rate limits
67
68The SDK exposes `AcmeRateLimited` and an optional `retryAfter` Effect `Duration`.
69Honor that hint when scheduling the next attempt. The [runtime Worker example](/acme/runtime#assemble-the-worker)
70returns HTTP 429 and a `Retry-After` header when the hint is available.
71
72SDK retries apply to individual protocol requests, not a durable end-to-end
73certificate job. If you need protocol errors to surface without the default
74request retries, add `@distilled.cloud/acme` as a direct dependency and provide
75its retry policy around the operation:
76
77```typescript
78import * as Acme from "@distilled.cloud/acme";
79
80const issued = yield* issuer.issue({
81 identifiers: ["api.example.com"],
82 solver,
83}).pipe(Acme.Retry.none);
84```
85
86Disabling retries does not bypass the CA's limits. Keep renewal jobs serialized,
87reuse valid stored certificates, and avoid an unbounded retry around `issue`.
88
89## Staging certificates are untrusted
90
91A Let's Encrypt staging certificate can be correctly issued and still fail
92browser trust checks. Use staging to validate your flow, then switch to a
93production CA in the intended stage. Uploading a staging chain to a public
94consumer does not make it trusted.
95
96## ZeroSSL rejects account registration
97
98Confirm that `eab.keyId` and `eab.hmacKey` are the matching CA-issued pair. The
99HMAC value is not your ZeroSSL API key. Keep its base64url encoding intact, load
100it as a redacted secret, and verify that the account uses `ACME.ZeroSSL` rather
101than another CA's directory.
102
103## A CA works locally but fails in a Worker
104
105Check connectivity from the deployed host, not only from your laptop. Live tests
106encountered Let's Encrypt TLS failures from Cloudflare Workers, so the deployed
107example uses ZeroSSL. For private CAs, `trustedRoot` currently relies on Bun's
108fetch TLS option and is not a portable Worker trust-store override.
109
110A Cloudflare runtime solver waits 60 seconds by default. Ensure the request and
111client remain alive long enough. Use a durable job design for issuance that must
112continue independently of a disconnected HTTP client.
113
114## A deployed certificate has not renewed
115
116Check that a deployment actually ran against the correct stack and stage with
117access to the same state. Renewal is evaluated on deployment; it has no
118background scheduler. Verify that `notAfter` is within `renewBefore`, then check
119whether the consumer loaded the updated output.
120
121The resource's read operation uses persisted certificate attributes rather than
122checking revocation status at the CA. If you revoked a certificate out of band,
123do not assume the next read will detect it and issue a replacement automatically.
124
125## Certificate and private key do not match
126
127Use the certificate's `chain` and `privateKey` from the same issuance result.
128The account private key signs ACME requests and cannot serve as the TLS key.
129After renewal, update both values together and reload the TLS consumer.