Safeguard
Industry Analysis

How to automate TLS certificates with Let's Encrypt

A step-by-step guide to automating TLS certificates with Let's Encrypt using certbot and cert-manager, plus verification steps so renewals never silently fail.

Karan Patel
Cloud Security Engineer
8 min read

Expired certificates still take down production more often than almost any other preventable outage — a browser warning, a failed API handshake, an on-call page at 2 a.m. over something that was entirely avoidable. The root cause is almost never a lack of certificates; it's manual renewal. Someone rotated a cert by hand eighteen months ago, the calendar reminder got deleted, and now a 90-day Let's Encrypt certificate has quietly lapsed in production.

This guide walks through how to automate TLS certificates with Let's Encrypt end to end: issuing your first certificate, setting up unattended renewal with certbot, understanding the ACME protocol automation that makes this possible, and extending the same pattern to Kubernetes with cert-manager. By the end, you'll have a renewal pipeline that requires zero human intervention — and a way to verify it's actually working before it's tested by an outage.

Step 1: Why You Must Automate TLS Certificates With Let's Encrypt

Let's Encrypt deliberately issues short-lived certificates — 90 days by default — specifically to force automation. The reasoning, straight from the Internet Security Research Group (ISRG) that runs Let's Encrypt, is that short lifetimes limit the damage window if a private key is compromised, and they push the entire ecosystem away from error-prone manual issuance toward scripted renewal.

This is a deliberate constraint, not a limitation to work around. If your renewal process depends on someone remembering to run a command, you don't have a certificate strategy — you have a countdown timer. The fix is to treat certificate issuance and renewal as infrastructure-as-code from day one, using the ACME (Automatic Certificate Management Environment) protocol that Let's Encrypt standardized specifically to make this possible.

Step 2: Install and Configure Certbot

Certbot is the most widely deployed ACME client and the reference implementation most sysadmins reach for first. This certbot setup tutorial covers a standard Linux host running Nginx, but the same core commands apply across Apache, standalone, and DNS-01 workflows.

Install certbot via your distro's package manager or snap (snap is recommended by the EFF for staying current):

sudo apt update
sudo apt install snapd
sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Issue a certificate and let certbot configure Nginx automatically:

sudo certbot --nginx -d example.com -d www.example.com

Certbot will validate domain ownership using the HTTP-01 challenge by default, write the certificate to /etc/letsencrypt/live/example.com/, and edit your Nginx server block to reference it. For hosts that can't expose port 80 (internal APIs, wildcard certificates), use the DNS-01 challenge instead with a plugin for your DNS provider:

sudo apt install python3-certbot-dns-cloudflare
sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "*.example.com" -d example.com

Wildcard certificates require DNS-01 — Let's Encrypt does not support HTTP-01 validation for wildcard names.

Step 3: Automate Renewal With a Scheduled Job

This is the step that actually solves the problem. Certbot ships a renewal command that checks every certificate on the host and only renews the ones within 30 days of expiry, so it's safe to run frequently:

sudo certbot renew --dry-run

Run the dry run first to confirm the renewal path works without burning against Let's Encrypt's rate limits. Once it passes, most modern certbot installs already register a systemd timer or cron entry automatically — verify it exists rather than assuming:

systemctl list-timers | grep certbot

If nothing is scheduled, add a cron job that runs twice daily, which is what the EFF recommends since it retries silently if one attempt fails due to a transient network issue:

0 0,12 * * * root certbot renew --quiet --deploy-hook "systemctl reload nginx"

The --deploy-hook flag is what most manual setups miss: renewing the certificate file on disk does nothing if the web server process never reloads it into memory. Always pair renewal with a reload or restart hook for whatever service terminates TLS.

Step 4: Understand the ACME Protocol Underneath

It helps to know what certbot is actually doing, because it explains why automation is safe and why some failure modes happen. ACME protocol automation works through a challenge-response exchange between your client and Let's Encrypt's servers:

  1. Your client requests a certificate for a domain and receives a set of challenges (HTTP-01, DNS-01, or TLS-ALPN-01).
  2. You (or your tooling) prove control of the domain by placing a token at a well-known HTTP path, a DNS TXT record, or via a TLS extension.
  3. Let's Encrypt's servers validate the challenge from multiple network vantage points.
  4. On success, the CA issues a signed certificate bound to an account key that persists across renewals.

Because the entire exchange is machine-to-machine and cryptographically verifiable, there's no human review step to bottleneck — which is precisely why automation is not just possible but the intended design. Any tool that speaks ACME (certbot, acme.sh, lego, cert-manager) is interacting with the same protocol underneath.

Step 5: Automate Certificates in Kubernetes With cert-manager

Long-lived VMs are only half the story — most teams now run workloads on Kubernetes, where certbot's file-based model doesn't fit well. This is where cert-manager kubernetes letsencrypt integration takes over, managing certificates as native Kubernetes resources reconciled by a controller instead of a cron job.

Install cert-manager via Helm:

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager --create-namespace \
  --set installCRDs=true

Define a ClusterIssuer pointing at Let's Encrypt's production ACME endpoint:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: security@yourcompany.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: nginx

Then annotate an Ingress resource to request a certificate automatically:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app-service
                port:
                  number: 443

cert-manager watches the Ingress, requests a certificate through the ClusterIssuer, stores it as a Kubernetes Secret named app-tls, and reissues it automatically well before expiry — no cron job, no server login required, and the renewal state lives in the cluster alongside everything else you already reconcile with GitOps.

Step 6: Verify and Troubleshoot the Automation

Automation you haven't tested is a hypothesis, not a control. Before trusting any of the above in production, verify each layer independently.

Check certificate expiry and chain from the outside:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -issuer

Confirm certbot's renewal timer is actually enabled, not just installed:

systemctl status certbot.timer

On cert-manager, check the Certificate resource's status directly — a Ready: True condition means the secret is populated and current:

kubectl describe certificate app-tls -n default
kubectl get challenges -A

Common failure modes worth knowing:

  • Rate limit errors (too many certificates already issued) — Let's Encrypt limits issuance per registered domain per week. Always test against the staging environment (--staging for certbot, letsencrypt-staging server for cert-manager) before running real issuance repeatedly.
  • HTTP-01 challenge failing behind a load balancer — the validation request must reach the ACME client, not get redirected to HTTPS or blocked by a WAF rule; temporarily allow-list /.well-known/acme-challenge/.
  • Renewal succeeds but the service still serves the old cert — almost always a missing or misconfigured deploy hook/reload step, not an ACME failure.
  • cert-manager stuck in Pending — check kubectl get order and kubectl get challenge for the specific validation error; DNS propagation delays are the most frequent culprit with DNS-01.

How Safeguard Helps

Automating issuance and renewal solves the outage problem, but it introduces a different one: certificates, private keys, and ACME account credentials become another category of software supply chain asset that has to be tracked, scoped, and audited — not just deployed and forgotten.

Safeguard gives security and platform teams visibility into that layer without adding manual process back into the pipeline. Instead of trusting that a cron job or a cert-manager controller is behaving correctly, teams get continuous monitoring across the certificate lifecycle: which services are relying on which issuers, whether private keys and ACME account keys are stored and scoped correctly, and whether renewal automation itself has drifted from policy — for example, a ClusterIssuer quietly pointed at a staging endpoint, or a certbot deploy hook that stopped firing after a server migration.

That visibility extends to the broader chain of trust these certificates depend on: the CI/CD pipelines that template Ingress manifests, the Helm charts that install cert-manager, and the secrets management feeding DNS-01 credentials into automation. If any of those upstream components are tampered with or misconfigured, a TLS renewal pipeline that looks healthy on paper can quietly become an attack surface. Safeguard treats certificate automation as part of the software supply chain it protects, not a separate concern — so teams that have correctly automated TLS certificates with Let's Encrypt can also prove, continuously, that the automation itself hasn't been compromised.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.