1---
2title: Certificate authorities
3description: Select a CA, configure external account binding, and understand account identity and key storage.
4---
5
6An `ACME.Account` is the identity used to sign orders with one certificate
7authority (CA). Certificates reference that account; changing the issuing account
8replaces the dependent certificate resource.
9
10## Choose a directory
11
12| Preset | Intended use | External account binding |
13| --- | --- | --- |
14| `ACME.LetsEncryptStaging` | Development; untrusted certificates | No |
15| `ACME.LetsEncrypt` | Publicly trusted production certificates | No |
16| `ACME.ZeroSSL` | ZeroSSL ACME issuance | Required |
17| `ACME.GoogleTrustServicesStaging` | Google Trust Services staging | Required |
18| `ACME.GoogleTrustServices` | Google Trust Services production | Required |
19
20A preset selects a directory URL, not an Alchemy subscription. CA eligibility,
21issuance limits, validity periods, and terms still apply. Check the CA's current
22requirements before deploying. The supplied Google presets do not imply that a
23Google account or external binding credentials are provisioned for you.
24
25## Start with staging
26
27Inside the stack from [Getting started](/acme/getting-started):
28
29```typescript
30const account = yield* ACME.Account("Issuer", {
31 ca: ACME.LetsEncryptStaging,
32 contact: ["mailto:ops@example.com"],
33 termsOfServiceAgreed: true,
34});
35```
36
37Staging exercises real account registration and DNS-01 validation. Its certificate
38chain is intentionally untrusted; a browser warning is expected even after a
39successful deployment.
40
41## Switch to production
42
43```diff lang="typescript"
44 const account = yield* ACME.Account("Issuer", {
45- ca: ACME.LetsEncryptStaging,
46+ ca: ACME.LetsEncrypt,
47```
48
49Changing the directory replaces the account and reissues dependent certificates.
50Use a separate production stage and state store when development certificates
51must remain independent. Schedule [renewal](/acme/renewal) before deploying a
52production certificate to a live service.
53
54## Configure ZeroSSL external account binding
55
56External account binding (EAB) associates an ACME account with a CA customer
57account. Obtain a matching key ID and HMAC key from ZeroSSL and put them in your
58secret configuration as `ZEROSSL_EAB_KID` and `ZEROSSL_EAB_HMAC_KEY`.
59
60```typescript
61import * as ACME from "alchemy/ACME";
62import * as Config from "effect/Config";
63
64export const Issuer = ACME.Account("Issuer", {
65 ca: ACME.ZeroSSL,
66 eab: {
67 keyId: Config.String("ZEROSSL_EAB_KID"),
68 hmacKey: Config.Redacted("ZEROSSL_EAB_HMAC_KEY"),
69 },
70 termsOfServiceAgreed: true,
71});
72```
73
74The HMAC key is the CA-issued base64url value, not your ZeroSSL REST API key.
75Keep the pair together. Yield `Issuer` in the stack or reference it from a
76certificate to register it. Google Trust Services uses the same `eab` shape with
77credentials obtained from Google.
78
79## Reuse an existing account resource
80
81```typescript
82const account = yield* Issuer;
83const certificate = yield* ACME.Certificate("Api", {
84 account,
85 identifiers: ["api.example.com"],
86 solver: Cloudflare.DNS.AcmeSolver({
87 zoneId: Config.String("CLOUDFLARE_ZONE_ID"),
88 }),
89});
90```
91
92Referencing the same account resource reuses its persisted signing key and account
93URL. EAB is used when registering the account; ordinary signed requests use the
94account key. Changing the EAB key ID replaces the account. Changing only the HMAC
95value does not force an existing account to register again.
96
97## Choose an account key algorithm
98
99```diff lang="typescript"
100 export const Issuer = ACME.Account("Issuer", {
101 ca: ACME.ZeroSSL,
102+ keyAlgorithm: "RS256",
103```
104
105The default is `ES256`; `RS256` is also supported. Changing the account algorithm
106replaces the account. A certificate's separate `keyAlgorithm` controls the key
107used by your TLS server, not the account signing key.
108
109## Use a custom directory
110
111```typescript
112export const PrivateIssuer = ACME.Account("PrivateIssuer", {
113 ca: {
114 directoryUrl: "https://ca.internal.example/acme/acme/directory",
115 },
116 termsOfServiceAgreed: true,
117});
118```
119
120Use this form for another ACME-compatible CA. It must offer DNS-01 challenges for
121the requested identifiers. Network access to the directory and authorization
122endpoints must be available wherever issuance runs.
123
124## Trust a private CA's HTTPS endpoint
125
126```diff lang="typescript"
127 ca: {
128 directoryUrl: "https://ca.internal.example/acme/acme/directory",
129+ trustedRoot: Config.String("ACME_CA_ROOT_PEM"),
130 },
131```
132
133`trustedRoot` is a PEM trust anchor for the CA's **API connection**, not the chain
134that your application serves. The current HTTP implementation supplies Bun's
135`fetch` TLS option; other runtimes may ignore it. Use Bun for this private-root
136path, or arrange platform-level trust and verify connectivity in your target
137runtime. It does not disable certificate verification.
138
139## Preserve account identity
140
141Alchemy generates the account key and stores it in stack state. `AccountProps`
142does not currently accept an existing account key or account URL, so importing an
143externally created account is not supported by this resource. The lower-level
144Distilled SDK can accept explicit credentials, but it does not import them into
145Alchemy's resource lifecycle.
146
147Back up state securely. Recreating a lost state file generates a new key rather
148than recovering the old account. A crash after CA registration but before the
149first state save can likewise leave an account whose key was not persisted.
150`Redacted` protects ordinary logs, not the storage backend.
151
152## Next steps
153
154- [DNS validation](/acme/dns-validation) — prove control of the requested names.
155- [Runtime issuance](/acme/runtime) — bind this account into an application.
156- [Account reference](/providers/acme/account) — all account props and outputs.