| 1 | --- |
| 2 | title: Renewal & revocation |
| 3 | description: Schedule renewal, understand replacement and deletion, and protect certificate and account keys. |
| 4 | --- |
| 5 | |
| 6 | An `ACME.Certificate` is checked when Alchemy plans a deployment. It has no |
| 7 | background timer. Keeping the stack deployed is not enough to keep its |
| 8 | certificates renewed. |
| 9 | |
| 10 | ## Choose a renewal window |
| 11 | |
| 12 | In the certificate from [Getting started](/acme/getting-started): |
| 13 | |
| 14 | ```diff lang="typescript" |
| 15 | const certificate = yield* ACME.Certificate("Site", { |
| 16 | account, |
| 17 | identifiers: ["example.com"], |
| 18 | solver: Cloudflare.DNS.AcmeSolver({ |
| 19 | zoneId: Config.String("CLOUDFLARE_ZONE_ID"), |
| 20 | }), |
| 21 | + renewBefore: "30 days", |
| 22 | }); |
| 23 | ``` |
| 24 | |
| 25 | Thirty days is the default. When less than this remains before `notAfter`, a |
| 26 | deployment issues a new certificate with a fresh private key and updates the |
| 27 | resource outputs. Choose a window shorter than the CA's certificate lifetime; |
| 28 | a longer window can cause issuance on every deployment. |
| 29 | |
| 30 | ## Schedule deployments |
| 31 | |
| 32 | ```sh |
| 33 | alchemy deploy --stage prod --profile prod --yes |
| 34 | ``` |
| 35 | |
| 36 | Run the same deployment regularly from your existing scheduler or CI runner. |
| 37 | Daily is a reasonable starting point for a 30-day renewal window. The runner |
| 38 | must use the same stack name, stage, protected state backend, source revision, |
| 39 | and necessary credentials as the production deployment. `--yes` approves the |
| 40 | plan, so review source changes separately and serialize deployments to that |
| 41 | stack. See [CI setup](/environments/ci) for credential configuration. |
| 42 | |
| 43 | ## Monitor expiry independently |
| 44 | |
| 45 | Return `certificate.notAfter` from the stack and feed it into your monitoring. |
| 46 | Also inspect the certificate actually served by your TLS endpoint: updating |
| 47 | Alchemy's state does not prove that an external service loaded the replacement. |
| 48 | Alert early enough to investigate DNS failures, rejected orders, or unavailable |
| 49 | credentials before the old certificate expires. |
| 50 | |
| 51 | ## Understand which changes issue again |
| 52 | |
| 53 | | Change | Result | |
| 54 | | --- | --- | |
| 55 | | Certificate is inside `renewBefore` | Update: issue a new certificate and key | |
| 56 | | Identifier set changes | Update: issue again | |
| 57 | | Certificate `keyAlgorithm` changes | Update: issue again | |
| 58 | | `preferredChain` changes | Update: issue again | |
| 59 | | Issuing account URL changes | Replace the certificate resource | |
| 60 | | Solver or propagation settings alone change | Keep the valid certificate; use new settings on later issuance | |
| 61 | | `revokeOnDelete` changes | Update deletion policy without issuing again | |
| 62 | |
| 63 | An ordinary renewal updates the stored certificate; it does not automatically |
| 64 | revoke the previous one. Retain any old material securely if your operational |
| 65 | process needs explicit revocation after cutover. |
| 66 | |
| 67 | ## Revoke on resource deletion |
| 68 | |
| 69 | ```diff lang="typescript" |
| 70 | const certificate = yield* ACME.Certificate("Site", { |
| 71 | account, |
| 72 | identifiers: ["example.com"], |
| 73 | solver: Cloudflare.DNS.AcmeSolver({ |
| 74 | zoneId: Config.String("CLOUDFLARE_ZONE_ID"), |
| 75 | }), |
| 76 | + revokeOnDelete: true, |
| 77 | }); |
| 78 | ``` |
| 79 | |
| 80 | This opts into CA revocation when that certificate resource is deleted, including |
| 81 | deletion of an old resource generation during replacement. It does not revoke |
| 82 | old certificates on every in-place renewal. Revocation uses the certificate's |
| 83 | own private key, so deleting an old certificate can still work after its account |
| 84 | has been deactivated. |
| 85 | |
| 86 | ## Destroy a stack deliberately |
| 87 | |
| 88 | ```sh |
| 89 | alchemy destroy --stage dev |
| 90 | ``` |
| 91 | |
| 92 | With the default `revokeOnDelete: false`, deletion forgets certificate state but |
| 93 | leaves the certificate valid at the CA. Account deletion deactivates the ACME |
| 94 | account; deactivation is distinct from revoking its issued certificates. Removing |
| 95 | a certificate from a TLS consumer is another separate operation. |
| 96 | |
| 97 | :::caution |
| 98 | Save any material needed for later revocation before destroying state. Once the |
| 99 | key is gone, you cannot rely on this stack to revoke the certificate. If the |
| 100 | certificate is still serving traffic, replace it at the consumer before revoking |
| 101 | it. |
| 102 | ::: |
| 103 | |
| 104 | ## Protect account and certificate keys |
| 105 | |
| 106 | The account's `privateKey` is a redacted private JWK used for ACME requests. The |
| 107 | certificate's `privateKey` is a redacted PKCS#8 PEM used by the TLS server. They |
| 108 | are separate keys with separate purposes. |
| 109 | |
| 110 | Restrict state access to trusted deployment identities, use encryption at rest |
| 111 | where your backend supports it, protect backups, and exclude local state from |
| 112 | source control. `Redacted` is a logging safeguard, not encryption. Avoid returning |
| 113 | keys as top-level stack outputs or unwrapping them for diagnostic logging. |
| 114 | |
| 115 | The certificate resource's read operation returns persisted attributes; it does |
| 116 | not query the CA for out-of-band revocation. Expiry checks alone are therefore |
| 117 | not a certificate-status monitoring system. |
| 118 | |
| 119 | ## Renew runtime-issued certificates |
| 120 | |
| 121 | For certificates issued through `IssueCertificate`, your application must store |
| 122 | the chain, key, expiry, and enough account/domain metadata to manage the result. |
| 123 | Schedule a new `issue` call before expiry, install the new material at the |
| 124 | consumer, verify it is serving, and only then retire the previous certificate. |
| 125 | |
| 126 | Serialize issuance per identifier set, persist jobs durably, and honor CA retry |
| 127 | hints. An in-memory Worker variable is not durable storage or a renewal schedule. |
| 128 | Destroying the account stack does not discover or clean up runtime-issued |
| 129 | certificates for you. |
| 130 | |
| 131 | ## Next steps |
| 132 | |
| 133 | - [Using certificates](/acme/using-certificates) — connect renewal outputs to a TLS consumer. |
| 134 | - [Runtime issuance](/acme/runtime) — issue and revoke through an application binding. |
| 135 | - [Troubleshooting](/acme/troubleshooting) — investigate a failed renewal. |