Short-Lived Certificates & ACME ARI
Let the CA tell each ACME client when to renew, and keep renewal working as TLS certificate lifetimes shrink to 47 days.
The SC-081 timeline
Ballot SC-081, adopted by the CA/Browser Forum in April 2025, shortens the maximum validity of publicly trusted TLS certificates and the period for which domain or IP validation may be reused:
| Certificates issued on or after | Maximum validity | Domain / IP validation reuse |
|---|---|---|
| Before 15 March 2026 | 398 days | 398 days |
| 15 March 2026 | 200 days | 200 days |
| 15 March 2027 | 100 days | 100 days |
| 15 March 2029 | 47 days | 10 days |
Subject identity information (organization details in OV / EV certificates) may be reused for 398 days from 15 March 2026.
A private CA operated in DuoKey is not bound by these rules, but clients and tooling built for the public web increasingly assume short lifetimes. With 47-day certificates a certificate is renewed roughly every month: renewal has to be automated, and its timing is best decided by the CA.
ACME Renewal Information (ARI)
ARI (RFC 9773) lets the CA tell each ACME client when to renew a given certificate, instead of every client applying a fixed rule such as "30 days before expiry", which does not fit 47-day or 7-day certificates. The DuoKey ACME server supports ARI on every CA directory.
Directory and renewal window
The directory https://<cockpit>/api/pki/acme/<ca-id>/directory advertises a renewalInfo URL. A client asks for the window of a certificate with:
GET /api/pki/acme/<ca-id>/renewal-info/<certID>
HTTP/1.1 200 OK
Retry-After: 3600
Content-Type: application/json
{"suggestedWindow": {"start": "2026-10-19T16:00:00Z", "end": "2026-10-20T20:00:00Z"}}certID is the base64url (unpadded) Authority Key Identifier and serial number of the certificate, separated by a dot, as defined in RFC 9773. Certificates issued by the DuoKey ACME server carry an Authority Key Identifier for that purpose.
| Certificate lifetime | Suggested renewal window |
|---|---|
| Any lifetime L | From two thirds of L to five sixths of L (i.e. from notAfter - L/3 to notAfter - L/6) |
| 90 days | Days 60 to 75 |
| 47 days | Days 31.3 to 39.2 |
| 7 days | Days 4.7 to 5.8 |
Retry-After is 1 hour for certificates of 10 days or less, 6 hours otherwise. An unknown certificate answers 404, a malformed certID 400.
Replacing a certificate
A new order can name the certificate it replaces with "replaces": "<certID>". The server checks that the certificate was issued by the same ACME directory, to the same ACME account, shares at least one identifier with the new order, and has not already been replaced by another order (otherwise 409 alreadyReplaced, also when two orders race).
Forcing early renewal
After an incident (CA key change, mis-issuance) an administrator can tell every ARI-aware client of a directory to renew now:
PUT /api/pki/acme/<ca-id>/ari-trigger
{"renew_issued_before": "2026-10-15T12:00:00Z"}Every certificate of that directory issued before that instant gets a renewal window in the past, so clients renew at their next ARI check (within Retry-After). null clears the trigger; GET returns it (Operations.Pki.Acme.Read). Changes are recorded in the audit log.
ACME certificate validity
ACME certificates are issued for the directory's configured maximum validity (1 to 3,650 days; 90 days by default), shortened to the order's notAfter when the client asks for an earlier date, and never beyond the CA's own expiry. For short-lived certificates, set the directory's maximum validity accordingly (for example 47 or 7 days).
Domain validation: http-01, dns-01, tls-alpn-01
When an ACME client answers a challenge, the DuoKey ACME server checks domain control itself before the order can be finalized. An internal CA on a trusted network can instead set ACME_AUTO_VALIDATE=1, which accepts every challenge without a probe (unchanged behaviour; do not use it where applicants are not trusted).
Which challenges an order gets
Each authorization offers the challenge types allowed on the ACME directory (allowed_challenge_types, in that order; http-01 when the CA has no ACME configuration), filtered per identifier:
| Identifier | Challenge types offered |
|---|---|
example.com | Every type allowed on the directory |
*.example.com (wildcard) | dns-01 only (RFC 8555 section 7.1.3). The authorization names example.com and carries "wildcard": true. If the directory does not allow dns-01, the new order is refused with rejectedIdentifier |
| IP address | http-01 only (tls-alpn-01 for IP addresses, RFC 8738, is not implemented) |
Malformed DNS identifiers (a.*.example.com, *.*.example.com, empty labels) are refused with rejectedIdentifier. Challenge tokens are 256-bit random values.
Order rule: every authorization must be valid
An order becomes ready (and can be finalized) only when every one of its authorizations is valid. An order for example.com and *.example.com therefore needs both challenges to succeed.
A failed challenge makes the challenge, its authorization and the order invalid (RFC 8555 section 7.1.6), with an ACME problem document (dns, connection, tls, incorrectResponse, rejectedIdentifier or unauthorized). The client then starts a new order. A challenge that reached a final state is not validated again.
Earlier releases marked a multi-identifier order ready as soon as one challenge was validated. A client that answered only some of the challenges must now answer all of them.
http-01
The server fetches http://<domain>/.well-known/acme-challenge/<token> and compares the answer with the key authorization. This is the default type and the only one available for IP address identifiers.
dns-01 (RFC 8555 section 8.4)
The server reads the TXT records at _acme-challenge.<domain> (for *.example.com: _acme-challenge.example.com) and accepts the challenge if one record equals base64url(SHA-256(keyAuthorization)).
- Several TXT records may be present (for example the wildcard and the base domain of the same order, or a stale value).
- CNAMEs are followed, so delegating
_acme-challengeto a dedicated validation zone works. - NXDOMAIN, an empty answer or a timeout fail the challenge with a
dnsproblem; records without the expected value fail withincorrectResponse. - No DNS answer is cached between attempts.
Use dns-01 for wildcard certificates and for hosts the cockpit cannot reach on port 80 or 443.
ACME clients publish the TXT record through their DNS provider plugin (cert-manager dns01 solver, certbot --preferred-challenges dns, lego --dns). The record must be visible from the resolver used by the cockpit before the client answers the challenge; with a slow-propagating DNS provider, configure the client's propagation wait.
Resolver settings and split-horizon DNS
| Variable | Default | Meaning |
|---|---|---|
ACME_DNS_RESOLVERS | (unset) | Comma-separated nameservers, IP or IP:port ([IPv6]:port for IPv6). When set, dns-01 queries go only to these servers |
ACME_DNS_TIMEOUT_SECS | 10 | Timeout per DNS query, 1 to 60 seconds |
Without ACME_DNS_RESOLVERS, the nameservers of the pod or host (/etc/resolv.conf) are used. With split-horizon DNS, where the cockpit must see the internal view of a zone (or the public one), point ACME_DNS_RESOLVERS at the resolver that serves that view:
env:
- name: ACME_DNS_RESOLVERS
value: "10.20.0.53,10.20.0.54"
- name: ACME_DNS_TIMEOUT_SECS
value: "5"tls-alpn-01 (RFC 8737)
The server resolves the domain, connects to port 443 offering only the ALPN protocol acme-tls/1 (SNI set to the domain), and accepts the self-signed certificate presented if:
acme-tls/1was negotiated;- its subjectAltName contains exactly one entry, a DNS name equal to the domain;
- it carries the critical
id-pe-acmeIdentifierextension (1.3.6.1.5.5.7.1.31) with the SHA-256 of the key authorization.
The certificate is self-signed by design, so no chain is validated, but the TLS handshake signature is checked: the peer holds the key of the certificate it presents. There are no redirects; each connection attempt times out after 10 seconds and at most four resolved addresses are tried. Not available for wildcard or IP identifiers.
Outbound connections
http-01 and tls-alpn-01 connect to an address derived from the requested identifier. If any address the name resolves to is loopback, link-local (including the cloud metadata address 169.254.169.254) or unspecified, the challenge fails with rejectedIdentifier and nothing is dialled. Private LAN addresses (RFC 1918, IPv6 ULA) stay allowed, because an internal CA validates internal hosts. The connection goes to the address that was checked, so a second DNS answer cannot redirect it. dns-01 only queries the configured resolvers and never connects to an address taken from the identifier.
Renewal triggers in DuoKey
| Certificates | What triggers renewal |
|---|---|
| Issued by the DuoKey ACME server to ACME clients (cert-manager, certbot, lego, …) | The client. ARI-aware clients follow suggestedWindow; others use their own rule (cert-manager: two thirds of the lifetime by default) |
| Issued by a DuoKey CA and deployed by Cockpit to a target | Renewal policies: threshold capped at half the lifetime, so a 47-day certificate with a 30-day threshold renews at about day 23 and a 7-day certificate does not loop |
| Any issued or discovered certificate | Expiry alerts warn the tenant at its thresholds |
47-day readiness
Ready today
- ARI on the built-in ACME server: renewal information,
replaces,alreadyReplaced, administrator-forced renewal. - ACME certificate validity follows the directory configuration, down to one day.
- Server-side renewal of deployed certificates that does not loop on short-lived certificates, with retries, backoff, alerting and single renewal across replicas.
- Expiry alerts per tenant (e-mail, Teams, Slack, webhooks), once per threshold.
- Kubernetes cert-manager through the ACME server.
- Domain validation by
http-01,dns-01(including wildcard certificates and split-horizon resolvers) andtls-alpn-01; an order is ready only when every authorization is valid.
Limits
- Validation is never reused: every order re-validates, which meets a 10-day reuse limit by construction (but saves no work either).
- Validation runs from a single vantage point, synchronously in the challenge request. There is no multi-perspective validation (required of public CAs by CA/Browser Forum ballot SC-067) and no CAA check. This suits a private CA; it is not the validation profile of a publicly trusted CA.
tls-alpn-01for IP address identifiers (RFC 8738) is not implemented.- Certificates issued over ACME are kept on their ACME order: they are not yet listed in the issued-certificate inventory and are not covered by expiry alerts or renewal policies.
- ACME
revoke-certis accepted but does not yet add the certificate to the CA's CRL or OCSP responses. Prefer short validity for ACME certificates so that compromise exposure stays bounded. - Cockpit's own ACME client (towards public CAs) does not read ARI from those CAs.
- Concurrent load is measured on a single node only, with auto-validated challenges (see Measured load). Throughput across several replicas and with real
http-01/dns-01validation is not measured. - The public protocol routes, ACME included, are limited to 1000 requests per minute per source address by default, which caps one address at about 100 ACME certificates per minute. Raise
DKE_PROTOCOL_RATE_LIMIT_PER_MIN(up to 100000) when a fleet reaches Cockpit from one egress address, for example cert-manager in a cluster behind NAT.
Measured load
A load test drives the ACME protocol against one Cockpit instance: N ACME clients, each with its own account, run in parallel; each client issues 7-day certificates and renews each one right after issuance (renewal information, then a new order with replaces, through finalize and download). Throughput counts issued plus renewed certificates over the wall time of the parallel phase.
Runs of 24 October 2026, release build, ECDSA P-256 CA, 7-day certificates, 40 database connections, one run per row:
| Clients x certificates per client | Issued | Renewed | Errors | Wall time | Certificates/s (issued + renewed) | Issue p50 / p95 / p99 | Renew p50 / p95 / p99 |
|---|---|---|---|---|---|---|---|
| 1 x 200 | 200 | 200 | 0 | 45.1 s | 8.9 | 104 / 128 / 141 ms | 115 / 139 / 155 ms |
| 8 x 25 | 200 | 200 | 0 | 10.0 s | 39.9 | 188 / 212 / 264 ms | 205 / 236 / 244 ms |
| 8 x 100 | 800 | 800 | 0 | 41.0 s | 39.0 | 192 / 230 / 293 ms | 210 / 248 / 275 ms |
| 16 x 13 | 208 | 208 | 0 | 10.8 s | 38.5 | 355 / 732 / 802 ms | 410 / 563 / 588 ms |
| 32 x 7 | 224 | 224 | 0 | 8.4 s | 53.3 | 558 / 748 / 757 ms | 621 / 686 / 697 ms |
| 32 x 25 | 800 | 800 | 0 | 30.8 s | 52.0 | 570 / 695 / 727 ms | 640 / 727 / 736 ms |
| 64 x 4 | 256 | 256 | 0 | 9.9 s | 51.7 | 1.16 / 1.39 / 1.39 s | 1.27 / 1.29 / 1.30 s |
| 64 x 16 | 1024 | 1024 | 0 | 44.2 s | 46.3 | 1.32 / 1.56 / 1.58 s | 1.44 / 1.59 / 1.61 s |
All runs completed without errors. Throughput levels off at about 40 to 53 certificates per second from 8 clients upward; more clients only add waiting time. The 10-second rows are shorter and noisier than the 30 to 45-second rows.
Test conditions, which you should keep in mind when reading the figures:
- One Cockpit instance, requests sent in-process (no network hop, no TLS), PostgreSQL 16 in Docker on the same machine: a developer laptop (AMD Ryzen AI 9 HX PRO 370, 12 cores / 24 threads, 55.6 GB RAM, Windows 11) also running an IDE and a browser.
- Challenges were accepted without a probe (
ACME_AUTO_VALIDATE=1). In production,http-01/dns-01validation (including DNS propagation) usually takes longer than issuance itself. - The per-address rate limit was raised to 100000 requests per minute for the test. With the default (1000), the single-client run was stopped after about 10 seconds.
- The test client fetches a fresh nonce before every signed request (10 requests per issuance, 11 per renewal); real ACME clients reuse the nonce of each response.
Read these figures as an order of magnitude, not a capacity guarantee.