# How To Automate Certificate Management With ACME, Auto-Renewal, and Zero-Touch Workflows Published: 2026-08-21 · Tags: Technical · Canonical: https://infisical.com/blog/automated-certificate-management --- Certificate expiry keeps causing outages at companies with mature infrastructure teams, and the postmortems rarely blame a missing cron job. Renewal was configured. It ran. The service went down anyway. Configuring renewal feels like automated certificate management, but the bar is higher: issuance, deployment, verification, and alerting all running without a person. The step left manual is usually where the outage starts. The timing matters more than it used to. Public TLS certificate lifetimes are shrinking on a fixed schedule: a 200-day maximum took effect in March 2026, 100 days will take effect in 2027, and 47 days will take effect in 2029. A renewal process that tolerates occasional manual attention today stops working entirely at that cadence. For teams evaluating SSL certificate management and automation, that translates into four concrete pieces: automated issuance over ACME (the Automatic Certificate Management Environment), a domain-validation method that fits your infrastructure, renewal that also reloads the service, and monitoring of the certificate clients actually receive. ## Why manual certificate management fails, and is about to fail faster You have a server that runs nginx with a 90-day certificate. When nginx starts, it reads the certificate file from disk and loads a copy into memory; that in-memory copy is what it presents to every client that connects. nginx won't read the file again until something tells it to reload. To handle renewal, someone adds a cron job. It runs daily, checks how long the certificate on disk has left, and reissues it when fewer than 30 days remain: ```shell # runs daily via cron days_left=$(cert_days_remaining /etc/nginx/certs/server.crt) # openssl x509 -enddate, parsed if [ "$days_left" -lt 30 ]; then issue-cert.sh # writes a fresh key and certificate to disk fi ``` On day 60, the job fires. It writes a new certificate to disk and reports success. But nothing in the script tells nginx to reload, so nginx keeps serving the copy it loaded on day 0. There are now two certificates: a fresh one in the file and the original in memory, 30 days from expiry. On day 90, the original expires, and every client connecting to the site gets `certificate has expired`. The file on disk has 60 days left. The cron job has reported success every day, and monitoring, which watches the job rather than the site, has seen nothing wrong. | Day | File on disk | Copy in nginx's memory | Clients see | | ----- | ----- | ----- | ----- | | 0 | Issued, 90 days left | Loaded at startup, 90 days left | OK | | 60 | Renewed, 90 days left | Original, 30 days left | OK | | 90 | 60 days left | Original, expired | certificate has expired | Renewing a certificate and deploying it are separate steps. The script handles the first but never attempts the second, and because nothing in this setup checks the second, the failure remains invisible until the day it takes the site down. ![Renewal cron writes a fresh certificate to disk while nginx keeps serving the copy it loaded at startup](https://images.ctfassets.net/rzezkvk1rm65/iP4f341DWvMytgXOkFmkt/c7cf5f87a1d43219029477b224cf2e05/automated-certificate-management-diagram-1.png) The usual fix is a second script that watches the certificate file and reloads nginx when it changes. That works. It also means every host now runs a renewal script, a file watcher, the account key it uses with the CA, the certificate's private key, and sometimes a DNS API token, all maintained by hand. Nothing outside the host knows the certificate exists, and nothing alerts anyone when the cron job itself silently dies. ![Every host runs its own renewal script, file watcher, and keys, with no inventory and no alerting](https://images.ctfassets.net/rzezkvk1rm65/z1kwXICjImjCp3mULZen0/72a3b016034481876e3990112cf1fed5/automated-certificate-management-diagram-2.png) A certbot deploy hook is the cleaner version of the same idea, since it runs only after a renewal actually succeeds. Either way, manual certificate management is a series of steps that each depend on a person or a hand-rolled script: 1. Know the certificate exists. 2. Notice the expiry date approaching. 3. Run the renewal. 4. Deploy the result. 5. Confirm the service picked it up. Any step can fail silently, and because nothing checks what clients are being served, the first alert is the outage itself. You’ll have seen, and been a victim of, a version of something like this before. The details differ, but the results look the same: * Microsoft Teams went down for hours in February 2020 after an [authentication certificate expired](https://www.ciodive.com/news/microsoft-Teams-outage-security-certificate/571639/) without being renewed. * An expired certificate in Ericsson's core network software [knocked out mobile data](https://www.computerweekly.com/news/252460251/IT-complexity-raises-concerns-of-certificate-related-outages) for tens of millions of O2 and SoftBank subscribers in December 2018. * Starlink lost service globally for several hours in April 2023, which SpaceX attributed to an [expired ground station certificate](https://www.datacenterdynamics.com/en/news/spacex-starlink-outage-caused-by-expired-ground-station-certificates/). Those are single-certificate stories. The everyday version is scale. Keyfactor's [2024 PKI and Digital Trust Report](https://www.keyfactor.com/blog/key-takeaways-from-the-2024-pki-digital-trust-report/) puts the average organization at roughly 80,000 internally trusted certificates, and most respondents couldn't say exactly how many they have. Across even hundreds of certificates, owned by different teams and expiring on staggered dates, some step of some chain is always close to failing, and five manual steps don't survive being run that many times. The same report reports that most organizations have multiple certificate-related incidents per year, with about 2.6 hours to identify each outage and another 2.7 hours to fix it. CyberArk's [certificate outage research](https://www.venafi.com/resource/CIO-Study-Certificate-Related-Outages-Continue-to-Plague-Organizations) found 72% of organizations had at least one in the previous 12 months. Manual renewal survived this long because a 398-day certificate leaves a lot of room for error. But that room is going away on a published timetable. In April 2025, the CA/Browser Forum passed [ballot SC-081v3](https://cabforum.org/2025/04/11/ballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods/), an Apple proposal that caps the validity of publicly trusted TLS certificates in stages: | Effective date | Maximum validity | Maximum domain validation reuse | | ----- | ----- | ----- | | March 15, 2026 | 200 days | 200 days | | March 15, 2027 | 100 days | 100 days | | March 15, 2029 | 47 days | 10 days | Domain validation reuse is how long a CA can keep trusting one proof that you control a domain. Once that falls to 10 days, a normal 47-day renewal schedule means fresh validation on nearly every issuance, so proving control has to be automated along with renewal and deployment. This is already being implemented: * Google's Chrome Root Program had proposed a 90-day maximum in its [Moving Forward, Together](https://www.chromium.org/Home/chromium-security/root-ca-policy/moving-forward-together/) roadmap back in 2023, and the industry ballot went further. * Let's Encrypt default certificates drop to [64 days in February 2027 and 45 days in February 2028](https://letsencrypt.org/2025/12/02/from-90-to-45), with domain validation reuse shrinking to 7 hours, and [six-day certificates](https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability) are already generally available. Let's Encrypt also [stopped sending expiration notification emails](https://letsencrypt.org/2025/01/22/ending-expiration-emails) in June 2025 because automation has made them redundant. Run the numbers on 47 days. The full validity period renews roughly 8 times a year. Still, clients renew early, and one renewing at two-thirds of the lifetime will run closer to twelve issuance cycles. Across 100 certificates, that's an issuance somewhere in the fleet every seven to eight hours. These limits apply to publicly trusted certificates, and an internal CA can issue whatever lifetime you configure. But while internal and public certificates will often use different issuers and validation methods, the automation, inventory, ownership, and monitoring around them should be the same. ## How the ACME protocol works [ACME](https://en.wikipedia.org/wiki/Automatic_Certificate_Management_Environment), the Automatic Certificate Management Environment, is a protocol that allows software to obtain certificates automatically, with no human involvement. The name is literal: it gives clients and CAs a shared, automated certificate management environment for validation and issuance. It originated with Let's Encrypt and the Internet Security Research Group, and the IETF standardized it as [RFC 8555](https://www.rfc-editor.org/rfc/rfc8555.html) in 2019. An ACME client and server exchange JSON over HTTPS. After the client fetches the directory and a nonce, every operation it sends is signed with its account key: 1. The client fetches the CA's directory of endpoint URLs and creates an account tied to a key pair it generated, or picks up the account it already has. 2. It creates an order listing the names it wants in the certificate. 3. The CA returns an authorization for each name, specifying the challenges it will accept, and the client completes one for each name that still needs validation. 4. Once all names are authorized, the client submits a CSR (certificate signing request) and downloads the signed certificate chain. 5. The client's installer plugin or deployment hook places the certificate where the service expects it and ensures the service loads it. That last step sits outside the protocol, which is exactly why it gets skipped. ![The ACME exchange between client, CA, and your web server or DNS zone](https://images.ctfassets.net/rzezkvk1rm65/4YO6OEQHfwotE6epGWTvVA/6595afb70c73ca87cecd239975209b43/automated-certificate-management-diagram-3.png) Renewal is another order through the same flow. If a previous validation is still valid, the CA can reuse it; otherwise, the client proves control again. Either way, the replacement certificate has to be deployed exactly like the original was. Neither side of the exchange is special. The same certbot invocation that works against Let's Encrypt works against any ACME directory URL, including one fronting a private CA, so switching CAs is a URL change rather than a tooling migration. That matters when a CA has an incident or your requirements change. It also means the client is a solved problem. [certbot](https://eff-certbot.readthedocs.io/en/latest/using.html#automated-renewals) covers most Linux hosts, acme.sh and win-acme cover shell-only boxes and Windows, and Caddy and Traefik ship with a client built in, so on those servers certificates arrive with no separate tool at all. On the other side, every major public CA now runs an ACME server, Let's Encrypt, ZeroSSL, Google Trust Services, DigiCert, and Sectigo among them, and so do [private CA platforms](https://infisical.com/docs/documentation/platform/pki/ca/acme-ca). The one extra step with private CAs is identity: an ACME account is anonymous on its own, so the CA hands you a key ID and secret, called [external account binding](https://infisical.com/docs/documentation/platform/pki/ca/acme-ca#guide-to-connecting-infisical-to-an-acme-compatible-ca) (EAB), that the client presents once at registration to tie the account to your customer account or certificate profile. ## HTTP-01 vs DNS-01: choosing a validation challenge Every challenge answers the same question from the CA: prove you control this domain. The two main types of challenges differ in where you put the proof. HTTP-01 puts it on your web server, DNS-01 puts it in your DNS records. ### HTTP-01: serve a token on port 80 The CA hands the client a token, the client serves it at `http:///.well-known/acme-challenge/`, and the CA fetches it. The [spec pins the challenge to port 80](https://letsencrypt.org/docs/challenge-types/), so that port has to be reachable from the internet. ![HTTP-01 validation: the ACME client writes a token on port 80 and the CA fetches it over HTTP](https://images.ctfassets.net/rzezkvk1rm65/VJBwQMka07z9wwMl0DtIk/b07b1454e1deadad11722014e900add1/automated-certificate-management-diagram-4.png) HTTP-01 is the easiest challenge to operate. Any web server can serve a static file, and no DNS access is involved. It's also how hosting platforms get certificates for customer domains: when a domain points to their servers, they can answer the challenge for that domain. The constraints are: no wildcard certificates, port 80 must stay open, and in a load-balanced pool, the token must be present on whichever backend the CA happens to reach. ### DNS-01: publish a TXT record The client publishes a TXT record at `_acme-challenge.` containing a hash of the token, and the CA checks DNS. Nothing about the serving host matters, which makes DNS-01 the answer for [wildcard certificates](https://letsencrypt.org/docs/challenge-types/), internal services that accept no inbound connections from the internet, and anything behind a CDN. ![DNS-01 validation: the client publishes a TXT record and the CA queries public DNS](https://images.ctfassets.net/rzezkvk1rm65/1RYusrVOWEQyRFi5R2XUrh/714abd707eb7bbf5e2470a8c19998768/automated-certificate-management-diagram-5.png) The cost is credential handling. The machine that completes the challenge needs API access to your DNS provider, and a token that can create DNS records can usually also change where your domain points. Scope the credential as narrowly as your provider allows, or delegate `_acme-challenge` via CNAME to a separate zone that exists only for validation, so the token holds power over nothing else. DNS propagation also adds delay, so clients wait and retry before asking the CA to verify. With certbot and Cloudflare, that comes down to a plugin and a credentials file holding an API token scoped to DNS edits on one zone: ```shell certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \ -d "*.example.com" -d example.com ``` The [plugin](https://certbot-dns-cloudflare.readthedocs.io/en/stable/) publishes the TXT record, waits for propagation, and removes the record after validation. Every major DNS provider has an equivalent. The default that serves most teams: HTTP-01 for a public web server that can answer on port 80, DNS-01 for everything else. Wildcards, internal hosts, and multi-server fleets all point at DNS-01, and the credential question is worth answering before the first certificate is issued rather than after the first incident. | | HTTP-01 | DNS-01 | | :---- | ----- | ----- | | The proof lives | On your web server, port 80 | In a DNS TXT record | | The CA has to reach | Your server, from the internet | Public DNS only | | Wildcard certificates | No | Yes | | Internal or firewalled hosts | No | Yes | | Credential to protect | None | DNS provider API token | | Setup | Trivial on any public web server | Needs a DNS provider with an API | A third option, TLS-ALPN-01, proves control inside the TLS handshake itself on port 443. It exists for environments where port 80 is blocked, supports no wildcards, and has thinner client support, so treat it as the fallback when neither of the main two fits. ## cert-manager: automated certificates on Kubernetes Inside Kubernetes, [cert-manager](https://cert-manager.io/docs/usage/certificate/) is the standard tool for certificate management automation, and it works the way everything else in Kubernetes works: you declare what you want, and a controller makes it true (and keeps it true). An `Issuer` or `ClusterIssuer` says where certificates come from, and a `Certificate` says what you want issued. The controller does the rest: runs the ACME exchange, stores the key and chain in a Secret that your Ingress or pods reference, and renews on schedule. ![cert-manager runs the ACME exchange, writes the key and chain to a Secret, and renews at two-thirds of the lifetime](https://images.ctfassets.net/rzezkvk1rm65/2tqxyaQ0lK31dzvb4ehNrL/e2dc32923229b43db1646e4d5dcaa04f/automated-certificate-management-diagram-6.png) A cluster-wide issuer for Let's Encrypt looks like this: ```yaml apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: ops@example.com privateKeySecretRef: name: letsencrypt-account-key solvers: - http01: ingress: ingressClassName: ``` Most workloads never need an explicit `Certificate` resource. Annotating an Ingress with `cert-manager.io/cluster-issuer: letsencrypt-prod` and giving it a `tls` block with the hosts and a `secretName` makes cert-manager provision and renew those certificates on its own. Hence, renewals reach clients without a manual step. By default, cert-manager renews once two-thirds of a certificate's lifetime has passed: day 60 for a 90-day certificate, day 31 for a 47-day one. The `renewBefore` field overrides the default. Because cert-manager speaks plain ACME, the `server` field accepts any directory URL, and `externalAccountBinding` in the issuer spec covers CAs that require EAB. The same manifest structure works whether the CA behind it is Let's Encrypt or a private one. ## The anatomy of an auto-renewal workflow Outside Kubernetes, renewal automation has four parts: 1. A trigger that decides when to renew 2. The renewal itself 3. A deployment step that makes the service pick up the new certificate 4. Monitoring that notices when any of it fails ![An auto-renewal workflow: trigger, renew, deploy, and a monitor that reads the served certificate](https://images.ctfassets.net/rzezkvk1rm65/sfZPdQp0UjyiRFTJ5WJOc/627c93ea879299be65211ff02fd575d5/automated-certificate-management-diagram-7.png) certbot handles the trigger out of the box. Installing it registers a systemd timer, or a cron entry on older systems, that runs `certbot renew` [twice a day](https://eff-certbot.readthedocs.io/en/latest/using.html#automated-renewals) at randomized times, and the command only acts on certificates approaching expiry. `certbot renew --dry-run` does a full practice renewal against the staging environment, so you can prove the setup before trusting it in production. Deployment is the step that renewal scripts most often skip. A renewed file on disk does nothing until the service reloads, so every renewal needs a hook attached: ```shell certbot renew --deploy-hook "systemctl reload nginx" ``` certbot also runs anything in `/etc/letsencrypt/renewal-hooks/deploy/` after each successful renewal, which is the better place for reload logic: a hook there fires no matter how renewal ends up being run, where a command-line flag only applies when someone remembers to pass it. The same idea applies to HAProxy, Apache, or anything else that loads certificates into memory. Then verify the whole path once by hand before trusting it: ```shell # note the certificate currently served echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -serial # test renewal and run the configured deploy hook sudo certbot renew --cert-name example.com --dry-run --run-deploy-hooks # optional one-time live test: this consumes real issuance capacity sudo certbot renew --cert-name example.com --force-renewal # rerun the first command: a new serial means renewal and reload both worked ``` During the dry run, the served serial stays put by design, since the hook runs against the live certificate rather than the staging one. The serial only changes after the forced renewal, and if it doesn't, the reload never happened. Finding that in a test beats finding it a month after a silent renewal. Monitor the certificate the service presents, and not the file on disk. A probe that connects to the endpoint, reads the served certificate's expiry, and alerts at a threshold catches every failure mode at once: renewal that never ran, renewal that ran and failed, and renewal that succeeded without a reload. ```shell echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \ | openssl x509 -noout -enddate ``` You should treat a failed renewal as an incident because the time between the failure and the expiry date is the only time you have to fix it. And don't wait for the CA to warn you: Let's Encrypt stopped sending expiration emails in 2025, so the alerting is yours to build. ## Zero-touch renewal: when the server handles everything Zero-touch certificate management means nobody writes the trigger, hook, or monitor, because the web server or proxy that serves HTTPS handles the entire lifecycle. ![Zero-touch renewal handled entirely by the proxy or cloud platform serving HTTPS](https://images.ctfassets.net/rzezkvk1rm65/3cpQgYRAY6TjKgI8dqYllc/1b2fbc891a13aa4741deee1c724beca5/automated-certificate-management-diagram-8.png) Caddy is the clearest example. [Automatic HTTPS](https://caddyserver.com/docs/automatic-https) is its default behavior: give it a domain, and it obtains a certificate, renews it when roughly a third of its lifetime remains, and swaps in the new certificate without dropping connections. Traefik ships a built-in ACME resolver, and recent HAProxy releases added native ACME support, so the approach now covers much of the proxy layer. Cloud providers offer the managed version. AWS Certificate Manager automates SSL certificate management in AWS: it issues public certificates, [renews them automatically](https://docs.aws.amazon.com/acm/latest/userguide/managed-renewal.html), and deploys renewal certificates to load balancers, CloudFront, and API Gateway with no machinery on your side. Google-managed certificates and Azure App Service-managed certificates work the same way inside their platforms. The constraint is that Caddy manages the certificates on that Caddy server, ACM manages the ones attached to AWS services, and neither can tell you about anything else. Both flavors are also built for public TLS first, and certificates from your own private CA mostly sit outside them. An estate that spans VMs, Kubernetes, multiple clouds, and internal services needs the same hands-off behavior from something that isn't confined to a single box or platform. ## How to automate certificate management with Infisical [Infisical's certificate manager](https://infisical.com/docs/documentation/platform/pki/overview) runs issuance, renewal, deployment, and visibility as one system: private CAs whose keys never leave the platform, certificate profiles that set the rules for what can be issued (allowed names, lifetimes, key types), certificate requests over API or ACME, and an inventory with expiry alerting and revocation across everything issued. Creating a [private CA](https://infisical.com/docs/documentation/platform/pki/ca/private-ca) and a certificate profile takes a few minutes in the dashboard, and external CAs can also issue profiles, so public and private certificates share a single inventory. There are two ways to use it: run Infisical's agent beside each service, or point the ACME clients you already have at Infisical directly. ![Infisical as one control plane for the agent, cert-manager, and any ACME client](https://images.ctfassets.net/rzezkvk1rm65/7s1oL3NBJfucInXXAh2Z6j/ff1e47e642af786fa2b62431838b58fd/automated-certificate-management-diagram-9.png) ### The agent route: one YAML file per host The [Infisical agent](https://infisical.com/docs/integrations/platforms/certificate-agent) runs alongside your service, authenticates as a [machine identity](https://infisical.com/docs/documentation/platform/identities/universal-auth), and manages the certificate end-to-end. Its configuration replaces a hand-rolled renewal script, a reload watcher, and any issuance credential stored on the host. This is Infisical's current applications-style configuration: ```yaml version: v1 # Infisical server configuration infisical: address: "https://app.infisical.com" # The URL of the Infisical instance (e.g. https://app.infisical.com, https://eu.infisical.com, https://your-self-hosted-instance.com) retry-strategy: max-retries: 3 max-delay: "5s" base-delay: "200ms" # Infisical authentication configuration auth: type: "universal-auth" # The authentication method to use (e.g. universal-auth, kubernetes-auth, azure-auth, gcp-id-token, gcp-iam, aws-iam) config: client-id: "your-client-id" client-secret: "your-client-secret" # Certificate configuration certificates: - profile-name: "prof-web-server-12345" project-slug: "my-project-slug" attributes: common-name: "api.example.com" alt-names: - "api.example.com" - "api-v2.example.com" key-algorithm: "RSA_2048" signature-algorithm: "RSA-SHA256" key-usages: - "digital_signature" - "key_encipherment" extended-key-usages: - "server_auth" ttl: "30d" lifecycle: renew-before-expiry: "14d" # Renew 14 days before expiration status-check-interval: "6h" # Check certificate status every 6 hours file-output: private-key: path: "/etc/ssl/private/api.example.com.key" permission: "0600" certificate: path: "/etc/ssl/certs/api.example.com.crt" permission: "0644" chain: path: "/etc/ssl/certs/api.example.com.chain.crt" permission: "0644" post-hooks: on-issuance: command: "systemctl reload nginx" timeout: 30 on-renewal: command: "systemctl reload nginx && logger 'Certificate renewed'" timeout: 30 ``` `infisical cert-manager agent --config agent.yaml` starts the loop. The agent requests a certificate against the profile, writes the key, certificate, and chain where the service expects them, renews at the configured threshold, and fires the post-hook so the service reloads. The CA key stays in Infisical, the host holds a credential that can request certificates and nothing else, and every certificate the agent obtains appears in the central inventory. Verifying it takes minutes, not a two-month wait, for the first real rotation. Against a test profile that allows short lifetimes, set `ttl` and `renew-before-expiry` to a few minutes and watch the served serial change on every cycle while requests keep succeeding, then restore production values. | Hand-rolled piece | Agent equivalent | | ----- | ----- | | Renewal cron that checks expiry daily | `lifecycle.renew-before-expiry` | | File watcher that reloads the service | `post-hooks.on-renewal` | | Issuance script plus a CA key or DNS token on the host | Machine identity scoped to one certificate profile | | Spreadsheet of what exists where | Central inventory with expiry alerting | One step the agent can't do for you: if the profile issues from a private CA, the root has to reach every client trust store that will connect. The chain file makes nginx send the leaf and intermediates. It does nothing to make clients trust the root. ### The ACME route: keep the clients you have Each certificate profile can also expose an [ACME directory URL](https://infisical.com/docs/documentation/platform/pki/applications/enrollment-methods/acme), which makes Infisical an ACME server. Point any standard client at it with the profile's EAB credentials, and the workflow is identical to Let's Encrypt except the issuer is your private CA: ```shell sudo certbot certonly \ --standalone \ --server "" \ --eab-kid "" \ --eab-hmac-key "" \ -d api.example.com \ --email admin@example.com \ --agree-tos \ --non-interactive \ --deploy-hook "systemctl reload nginx" ``` certbot's own timer handles renewal from there, and the deploy hook runs again on every renewal. The acceptance test is the same as with any ACME CA: force one renewal and confirm the serial of the served certificate changes. The same applies here. `fullchain.pem` covers what nginx sends, and distributing the private root to client trust stores is still your job. ### What centralization removes The difference shows up at fleet scale. Without a control plane, 20 hosts means: * 20 ACME clients or renewal scripts to configure and patch * DNS API tokens are distributed to every machine that answers a DNS-01 challenge * per-team tracking of what was issued and when * No single place to revoke a certificate or see which ones expire this month With one, issuance policy lives in the certificate profile, renewal and deployment are two lines of agent config or a stock ACME client, and expiry alerts route to Slack, PagerDuty, or a webhook when something needs a human. And forgotten certificates get found because discovery scans pick up what nobody registered. ## Start with inventory, end with zero-touch A workable sequence, in order: 1. **Inventory what you have.** An `nmap --script ssl-cert` sweep of your address space finds everything currently serving TLS, and a crt.sh search on your domains shows every certificate public CAs have issued for them. Record expiry, issuer, and whoever currently renews each one. 2. **Put every certificate on ACME with a deploy hook attached.** certbot with `--deploy-hook` on hosts, a `ClusterIssuer` plus the ingress annotation in Kubernetes, and Caddy or Traefik, where replacing the proxy is an option. You're done when the dry run passes on every host, and one forced test renewal has reached clients. 3. **Monitor the served certificate from outside.** Run a scheduled probe that reads expiry from the live endpoint, whether that's the Prometheus blackbox exporter's `probe_ssl_earliest_cert_expiry` metric or an openssl `s_client` check in cron, and page when a renewal fails or time-to-expiry drops below your threshold. 4. **Centralize when the fleet grows.** The trigger is when you have more hosts running their own ACME clients than you want to patch by hand, a DNS token on more than one machine, or the first internal service that needs a private CA. At that point, move issuance policy, renewal, and alerting into one place, and day-to-day certificate work goes to zero. The 200-day maximum is already in effect, and 47 days arrives in March 2029. At that point, every public certificate renews roughly eight times a year, which only works when issuance, renewal, deployment, and monitoring run without a person in the loop. Whether that means certbot and a deploy hook on a single host or Infisical running the lifecycle across a fleet, the time to build it is while certificates still last 200 days.