Skip to main content
    Production Guide

    Certbot & ACME in Production: The Ultimate Automation Guide

    Automate TLS certificate issuance and renewal with Certbot and the ACME protocol. Covers HTTP-01, DNS-01, Docker, Kubernetes, renewal hooks, and production hardening — with copy-paste commands.

    My-SSL Security Team
    April 4, 2026
    18 min read

    What Certbot Is and How ACME Works

    If you've ever been jolted awake at 2 AM by a monitoring alert because a certificate expired on a production load balancer, you already know why automation matters. Certbot exists to make sure that never happens again.

    Certbot is an open-source ACME client — built and maintained by the Electronic Frontier Foundation — that handles requesting, installing, and renewing TLS certificates from any ACME-compatible certificate authority. Most people associate it with Let's Encrypt, but it's not locked to a single CA. Point the --server flag at a different ACME directory URL, and Certbot will happily talk to that CA instead.

    A couple of things worth knowing up front:

    • ACME (Automatic Certificate Management Environment) is an IETF standard defined in RFC 8555. It isn't proprietary — any CA can implement it.
    • Certbot's "vendor-neutral" design means your automation scripts don't break if you switch CAs down the road.

    ACME Objects and the Issuance Flow

    Here's what actually happens under the hood when Certbot requests a certificate. The ACME protocol coordinates around a handful of core objects — account, order, authorization, challenge, and certificate — and the whole dance looks like this:

    1. Account registration — Certbot creates (or authenticates) an account with the CA using JWS. Think of this as your identity with the CA.
    2. New order — Certbot says "I'd like a certificate for these domains."
    3. Authorizations & challenges — The CA responds with challenge options (HTTP-01, DNS-01, or TLS-ALPN-01) for each domain.
    4. Challenge response — Certbot publishes the proof — a file on port 80, a DNS TXT record, or a TLS handshake token.
    5. Validation — The CA reaches out and verifies you actually control those domains.
    6. Finalize — Certbot submits a CSR; the CA signs and returns your certificate chain.
    7. Installation — Certbot drops the cert into place and reloads your web server (if you've configured it to do so).

    Pro tip: Your ACME account key is your identity with the CA — not the certificate itself. If you lose the account key, you can still get new certs, but you won't be able to manage existing orders. Back it up.

    Renewal Is the Same Protocol, Repeated Safely

    There's no special "renewal" endpoint in ACME. Renewal is just a fresh order placed before the current cert expires. Let's Encrypt issues 90-day certificates and recommends renewing at the 60-day mark, giving you a solid 30-day buffer.

    Certbot handles this automatically through certbot renew, which checks all your certificates and only renews the ones that are due. Always test with certbot renew --dry-run after any config change — it'll catch problems before they bite you in production. Prefer not to run an ACME client yourself? You can get a free auto-renewing SSL certificate through a managed dashboard instead.


    ACME Validation Methods

    Before you can get a certificate, you need to prove you control the domain. ACME gives you three ways to do this, and picking the right one will save you real headaches later.

    For most teams, HTTP-01 is the right starting point — it's simple, it works, and it doesn't require DNS API access. DNS-01 is where things get interesting, and it's where most production setups end up, especially once wildcard certs or CDN-fronted architectures enter the picture.

    Comparison Table

    ChallengeWhat You ProveNetwork RequirementsWildcardsBest ForWatch Out For
    HTTP-01You serve content on the domainCA must reach port 80 and fetch a token file under /.well-known/acme-challenge/NoSimple single-host web serversPort 80 blocked; redirect loops; multi-server token sync
    DNS-01You control the domain's DNS zoneCA queries DNS for a TXT record at _acme-challenge.<domain>YesWildcards, multi-region setups, CDN/LB-fronted servicesDNS API credential exposure; propagation delays can bite you
    TLS-ALPN-01You control TLS on the domainCA connects to port 443 using ALPN "acme-tls/1" and validates a special certNoEnvironments where port 80 simply isn't an optionRequires specialized ACME client support (not Certbot — yet)

    HTTP-01 Flow and Security Notes

    How it works: The CA says "put this token at this URL on your domain." Certbot writes the file; the CA fetches it over plain HTTP.

    Things to watch for:

    • HTTP-01 won't work for wildcard certificates — full stop.
    • If you're running multiple web servers behind a load balancer, every backend needs to be able to serve the challenge token. We've seen teams spend hours debugging this because their LB was routing the CA's request to the wrong backend.
    • Some web server configs block or redirect /.well-known paths. Make sure /.well-known/acme-challenge/ is served as-is, with no redirects.

    DNS-01 Flow and Security Notes

    How it works: Certbot constructs a proof using the challenge token and your account key, then publishes it as a DNS TXT record at _acme-challenge.<domain>. The CA queries DNS and verifies the value.

    Here's the thing about DNS-01 — it's powerful, but the security implications are real. DNS API credentials that can create arbitrary TXT records can often do much more than that. Let's Encrypt explicitly warns about this: if those credentials leak, an attacker could potentially take over your entire domain.

    Our recommendation: Use narrowly scoped API tokens (Cloudflare's "Edit zone DNS" permission, for example) and consider running DNS validation from a separate, hardened machine that copies certificates to production afterward.

    Also, watch out for propagation delays. We've seen DNS-01 fail because a provider advertised a 30-second TTL but actually took 3+ minutes to propagate. If you're hitting intermittent failures, bump the propagation wait time.

    TLS-ALPN-01 and the Certbot Reality

    TLS-ALPN-01 (RFC 8737) lets you prove domain control through a special TLS handshake on port 443, which is handy when port 80 is completely off the table.

    But here's the catch: Certbot doesn't support TLS-ALPN-01 yet. If you need it, you'll have to look at alternative ACME clients like Caddy or lego. For most setups, though, you can work around this with DNS-01.


    Using Certbot for Automated Issuance and Renewals

    Let's get practical. This section covers installation, plugin choices, and the most common deployment patterns.

    Installation Overview and Directory Layout

    The Certbot project recommends installing via snap on most Linux systems. This keeps you on the latest version and avoids the stale-package problem that plagues distro repositories.

    Certbot organizes everything under /etc/letsencrypt/:

    • live/<cert-name>/ — symlinks to your current cert, key, and chain files
    • renewal/<cert-name>.conf — renewal configuration for each certificate
    • archive/<cert-name>/ — historical versions of your certs

    Ubuntu with systemd (snap + timer)

    Install Certbot and set up the binary:

    sudo snap install --classic certbot
    sudo ln -s /snap/bin/certbot /usr/local/bin/certbot

    Now grab a certificate. You've got two main options:

    Option A: Nginx plugin (auto-configures your Nginx server blocks)

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

    Option B: Webroot (zero downtime — your web server keeps running)

    sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com

    Always test that renewal works:

    sudo certbot renew --dry-run

    And verify the timer is active:

    systemctl list-timers | grep -i certbot
    journalctl -u snap.certbot.renew.service --no-pager -n 200

    Same snap flow as Ubuntu — Certbot's docs recommend it across both distributions. If you must use Debian packages, just make sure you're getting a recent enough version and confirm that a cron job or systemd timer was created for auto-renewal.

    CentOS/RHEL-Family Notes

    Here's where things can get tricky. CentOS/RHEL packaging and lifecycle differences mean the distro-provided Certbot package might be significantly outdated. We've seen snap vs. apt/yum conflicts cause silent failures — our advice is to pick one installation method and stick with it.

    If snap isn't available on your platform, OS package installs work, but verify the version and renewal mechanism manually. Certbot's own docs warn that third-party packages can fall out of date quickly.

    Apache Integration

    The Apache plugin works the same way as the Nginx one — it obtains and installs certificates automatically:

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

    Reverse Proxies, CDNs, and Load Balancers

    The right approach depends on where TLS terminates:

    • TLS terminates on an Nginx/Apache reverse proxy you control: The --nginx or --apache plugins are usually the simplest path.
    • TLS terminates on a managed load balancer or CDN: You probably can't install certs directly on the edge device. The common pattern is to obtain certificates on a management host using DNS-01, then push them to the LB/CDN via that provider's API.
    • CDN in front of your origin: HTTP-01 can still work if the CDN forwards /.well-known/acme-challenge/ to your origin without caching or rewriting. But DNS-01 sidesteps all CDN complications and is the safer baseline.

    Multi-Domain (SAN) and Wildcard Certificates

    Multi-Domain (SAN)

    Need one certificate covering multiple hostnames? Just stack -d flags:

    sudo certbot certonly --nginx \
      -d example.com -d www.example.com -d api.example.com

    Let's Encrypt fully supports SAN certificates — you can include up to 100 names in a single cert.

    Wildcard Certificates

    Wildcards require DNS-01 validation — there's no way around this:

    sudo certbot certonly \
      --dns-cloudflare \
      -d "example.com" -d "*.example.com"

    Important: If you used --manual for the DNS challenge, Certbot won't be able to auto-renew that certificate. You'll need to either use a DNS plugin or write custom auth hooks for unattended renewals.


    DNS-01 Automation with Cloudflare and Route 53 Plugins

    Manual DNS-01 is fine for a one-off test, but in production you want a plugin that creates and cleans up TXT records automatically. Here are the two most common setups.

    Snap Install of DNS Plugin (Cloudflare Example)

    sudo snap install certbot-dns-cloudflare
    sudo snap set certbot trust-plugin-with-root=ok

    Create a credentials file (and lock it down):

    # /root/.secrets/certbot/cloudflare.ini
    dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
    sudo chmod 600 /root/.secrets/certbot/cloudflare.ini

    Now issue your cert:

    sudo certbot certonly \
      --dns-cloudflare \
      --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
      -d "example.com" -d "*.example.com"

    Pro tip: Use a Cloudflare API token scoped to "Zone: DNS: Edit" for just the zone you need — not a global API key. The blast radius matters.

    Route 53 Plugin

    The Route 53 plugin handles TXT record creation via the AWS API. Your AWS credentials come from the usual places — environment variables, instance profile, IAM role, or shared credentials file.

    sudo certbot certonly \
      --dns-route53 \
      -d "example.com" -d "*.example.com"

    Generic DNS-01 Automation (Pseudo-Code)

    If your DNS provider doesn't have an official plugin, you can write custom hooks. The logic looks like this:

    function Present(domain, token, keyAuth):
      txtValue = Base64Url(SHA256(keyAuth))
      CreateTXTRecord("_acme-challenge." + domain, txtValue)
      WaitForDNSPropagation()
    
    function Cleanup(domain):
      DeleteTXTRecord("_acme-challenge." + domain)

    Docker Workflows

    Running Certbot in Docker? It works, but there are a few gotchas that catch people constantly.

    The #1 mistake we see: forgetting to persist /etc/letsencrypt as a Docker volume. If that directory lives inside the container and the container gets recreated, you lose your account keys, your renewal configs, and your certificate history. Worse, you'll likely hit Let's Encrypt rate limits trying to re-issue everything.

    HTTP-01 Standalone in Docker

    This approach binds port 80 directly. You'll need to stop your webserver temporarily:

    docker run --rm -it \
      -p 80:80 \
      -v /etc/letsencrypt:/etc/letsencrypt \
      -v /var/lib/letsencrypt:/var/lib/letsencrypt \
      -v /var/log/letsencrypt:/var/log/letsencrypt \
      certbot/certbot certonly --standalone \
      -d example.com \
      --email admin@example.com --agree-tos --non-interactive

    DNS-01 Using Route 53 Plugin Image

    docker run --rm -it \
      -v /etc/letsencrypt:/etc/letsencrypt \
      -v /var/lib/letsencrypt:/var/lib/letsencrypt \
      -v /var/log/letsencrypt:/var/log/letsencrypt \
      -e AWS_ACCESS_KEY_ID="YOUR_AWS_ACCESS_KEY" \
      -e AWS_SECRET_ACCESS_KEY="YOUR_AWS_SECRET_KEY" \
      certbot/dns-route53 certonly --dns-route53 \
      -d "example.com" -d "*.example.com" \
      --email admin@example.com --agree-tos --non-interactive

    Kubernetes: cert-manager Integration for ACME Automation

    If you're running Kubernetes, cert-manager is the way to go — and honestly, it's the only sane approach at scale. Rather than running Certbot as a sidecar or cron job in individual pods, cert-manager handles ACME issuance and renewal as a native Kubernetes resource.

    Minimal ClusterIssuer for ACME

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

    Request a Certificate

    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: example-com
    spec:
      secretName: example-com-tls
      dnsNames:
      - example.com
      - www.example.com
      issuerRef:
        name: acme-issuer
        kind: ClusterIssuer

    Need wildcards in Kubernetes? Switch the solver to DNS-01 — same principle, same _acme-challenge TXT records, but cert-manager handles it all declaratively.


    Automated Renewals, Hooks, and Production Best Practices

    Understand Your Renewal Mechanism (Cron vs systemd)

    Most Certbot installs ship with renewal already scheduled — but you'd be surprised how often this gets broken during upgrades or container rebuilds. Always verify that a timer or cron job actually exists.

    MechanismProsConsHow to Inspect
    systemd timerCentralized logs via journald, dependency orderingNeeds systemd familiaritysystemctl list-timers, journalctl -u <service>
    cronSimple, portable, works everywhereLogs can be scattered; no dependency controlCheck /etc/crontab, /etc/cron.*/* for certbot renew

    A Safe Default Renewal Schedule

    Let's Encrypt recommends renewing 90-day certificates around the 60-day mark. Run renewal checks twice daily — Certbot's smart enough to only renew certificates that are actually due, so frequent checks don't cause problems.

    Here's the cron entry from Certbot's own docs (with randomized sleep to avoid thundering herds):

    SLEEPTIME=$(awk 'BEGIN{srand(); print int(rand()*(3600+1))}'); \
    echo "0 0,12 * * * root sleep $SLEEPTIME && certbot renew -q" | \
    sudo tee -a /etc/crontab > /dev/null

    Reloading Services Safely with Hooks

    Here's the part most people forget, and it's the part that causes outages: the deploy hook. Certbot can renew your certificate perfectly, but if your web server doesn't reload, it'll keep serving the old (potentially expired) cert.

    Deploy hook to reload Nginx after renewal:

    sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
    sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'SH'
    #!/bin/sh
    systemctl reload nginx
    SH
    sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

    Certbot also supports pre and post hooks in /etc/letsencrypt/renewal-hooks/ — useful if you need to stop a service for standalone validation and restart it afterward.

    If your pipeline also signs binaries alongside TLS automation, note that code signing certificates and the hardware-key rule work differently — the private key has to live on a FIPS-certified token or HSM, so plan for a cloud or HSM-backed signing setup rather than reusing the same automation pattern.


    Staging vs Production, Dry Runs, and Rate Limits

    Don't learn about rate limits the hard way. Let's Encrypt enforces strict production limits (like 50 certificates per registered domain per week), and if you burn through them during testing, you're stuck waiting.

    Always start with staging:

    sudo certbot certonly --nginx \
      --server https://acme-staging-v02.api.letsencrypt.org/directory \
      -d example.com

    Staging certificates won't be trusted by browsers, but they prove your automation works end to end. Once everything passes, switch to production.

    Key things to remember:

    • Persist your ACME account material and /etc/letsencrypt state, especially in containers. Losing this state means re-registering and potentially re-issuing — both of which count against rate limits.
    • Run certbot renew --dry-run after every configuration change. Make it a habit.

    Key Management, File Permissions, and Backups

    Your private keys never leave your server — Let's Encrypt doesn't generate or hold them. That means protecting /etc/letsencrypt is entirely your responsibility.

    Best practices we recommend:

    • Treat /etc/letsencrypt like a secrets vault. Private key files should be 0600, and the directory structure should be readable only by root and the processes that need the certs.
    • Back up regularly:

    - /etc/letsencrypt (keys, certificate lineages, renewal configs)

    - Any DNS plugin credential files

    • If you're restoring from backup, run a staging dry-run before trusting production renewal. Stale configs can surprise you.

    Monitoring and Alerting Recommendations

    Even with perfect automation, things go wrong — DNS providers have outages, servers get misconfigured during maintenance, containers get rebuilt without volumes. Two monitoring layers will catch these before your users notice.

    Certificate Expiry at the Service Edge (What Users See)

    Run a daily check against your public endpoints:

    Alert thresholds we recommend:

    • ⚠️ Warning at ≤21 days
    • 🚨 Critical at ≤7 days
    • 📟 Page on-call at ≤3 days
    echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
      | openssl x509 -noout -dates

    Renewal Job Health (What Automation Is Doing)

    Set up alerts for:

    • Any non-zero exit code from the renewal job
    • No successful renewal logged in the last N days (tune N based on your schedule)

    Check the logs at /var/log/letsencrypt/letsencrypt.log and your systemd/cron output.

    Pro tip: Use My-SSL's SSL Checker for quick external certificate validation, and set up SSL Expiry Reminders as a safety net — because the best monitoring is the kind that doesn't depend on the same infrastructure it's monitoring.


    Troubleshooting and Common Failure Modes

    These are the issues we see most often in support conversations. If you're stuck, start here.

    HTTP-01 Failures

    What you'll see: The CA can't fetch /.well-known/acme-challenge/...

    Check these first:

    • Is port 80 actually reachable from the internet? (Not just from your local network.)
    • Is your web server serving /.well-known/acme-challenge/ without blocking or redirecting it?
    • If you're behind an LB or CDN, is the challenge path being forwarded to your origin correctly?
    curl -I http://example.com/.well-known/acme-challenge/test
    sudo certbot -v certonly --webroot -w /var/www/html -d example.com

    DNS-01 Failures

    What you'll see: "TXT record not found" or "Incorrect TXT record"

    Debugging steps:

    • Query the authoritative nameserver directly to confirm the record exists:
    dig +short TXT _acme-challenge.example.com
    • If the record looks right but validation still fails, you're likely hitting a propagation delay. Increase the propagation wait time in your plugin config.
    • Double-check that your DNS API token is scoped correctly and hasn't expired.

    Rate Limit Failures

    What you'll see: Errors about issuance limits being exceeded.

    How to recover:

    Renewal Succeeds but Service Still Serves Old Certificate

    This almost always means your web server didn't reload after renewal. The fix: add a deploy hook that runs systemctl reload nginx (or the equivalent for your server).

    Also confirm your web server config points to /etc/letsencrypt/live/<name>/fullchain.pem and privkey.pem — not copied files. Certbot updates the symlinks in live/ on each renewal, so if you copied the files elsewhere, those copies are now stale.

    "Manual" Wildcard Certs Don't Auto-Renew

    If you used --manual for DNS-01 without an automation script, Certbot can't renew automatically. You'll need to switch to a DNS plugin or implement custom auth/cleanup hooks. This is the number one cause of unexpected wildcard certificate expirations.


    Migration from Manual Certificates to Certbot Automation

    If you're migrating from manually purchased or manually renewed certs, here's a battle-tested approach that minimizes risk:

    1. Inventory everything — document current certificates, their SANs, and where they're configured
    2. Stage first — obtain a new certificate with Certbot against staging, matching all FQDNs and SANs
    3. Update server config — point your web server to the Certbot-managed paths under /etc/letsencrypt/live/<cert-name>/
    4. Validate thoroughly — check with a browser, with openssl s_client, and with an external SSL checker
    5. Confirm renewal workscertbot renew --dry-run should pass cleanly
    6. Only then decommission the old manual renewal process

    If you need to change renewal options for an existing Certbot-managed certificate, look into certbot reconfigure — and resist the urge to hand-edit renewal conf files without testing afterward.


    Production Rollout Checklist

    • Choose your validation method: HTTP-01 if you can reliably serve port 80; DNS-01 for wildcards, multi-region, or CDN/LB edge cases
    • Test in staging first — always, before touching production
    • Persist your state: /etc/letsencrypt and related directories must survive container rebuilds
    • Verify automated renewal: Check cron/systemd timers; run certbot renew --dry-run
    • Add deploy hooks: Services must reload on successful renewal — this is non-negotiable
    • Set up external monitoring: Certificate-expiry alarms (≤21d warn, ≤7d critical, ≤3d page)
    • Alert on renewal failures: Watch renewal job logs — silence isn't always golden
    • Have a backup plan: Back up private keys, renewal configs, and document your restore steps

    Frequently Asked Questions

    Recommended

    Need Purchased SSL Instead of Let's Encrypt?

    Get OV/EV validation, warranty protection, and professional support with a purchased SSL certificate.

    DV SSL Certificate

    Starting at $3.99/year

    • Domain Validation
    • 5-Min Issuance
    • Unlimited Server Licenses
    • Up to $1.75M Warranty
    Browse SSL Certificates

    Frequently Asked Questions

    Recommended

    Check Your SSL Certificate Now

    Use our free SSL Checker to verify your certificate chain, expiry, and configuration — works with Let's Encrypt and purchased certs.

    SSL Checker Tool

    Starting at Free/year

    • Certificate Chain Validation
    • Expiry Monitoring
    • Configuration Check
    • Set Up Email Reminders
    Check SSL Certificate

    Complement Certbot with External Monitoring

    Even with automated renewals, set up free email reminders as a safety net. Get notified 30, 14, 7, and 1 day before any certificate expires.

    Set Up Free Reminders