applied · level 8

ACME & Let's Encrypt

Free, automated TLS — http-01 vs dns-01 vs tls-alpn-01.

200 XP

ACME & Let's Encrypt

Until 2015, getting a TLS certificate meant filling in a web form, paying a CA, copying base64 between portals, and remembering to do it all again in a year. Let's Encrypt and the ACME protocol erased that whole experience. Free certs, automated end-to-end, valid for 90 days, renewed by a cron job. The web went from "TLS is annoying and rare" to "TLS is on every site" in a few years.

What ACME actually is

RFC 8555 defines ACME — Automatic Certificate Management Environment — a protocol between an ACME client (your server, certbot, cert-manager) and an ACME CA (Let's Encrypt, ZeroSSL, Buypass, Google Public CA, your enterprise's stepCA).

The protocol exchanges JSON messages over HTTPS. The client says "I'd like a cert for example.com"; the CA says "prove you control example.com by doing X"; the client does X; the CA verifies, signs, returns the cert. All automated.

client                            ACME CA
   │                                 │
   ├── newAccount  ──────────────────►│
   │◄────────────  account URL       │
   │                                 │
   ├── newOrder (CN=example.com)  ──►│
   │◄────────────  order + auths     │
   │                                 │
   ├── (challenge: serve token at...)│
   │                                 │
   ├── notify "I'm ready"  ─────────►│
   │   (CA validates the challenge)  │
   │◄────────────  status: valid     │
   │                                 │
   ├── finalize (with CSR)  ────────►│
   │◄────────────  cert chain        │

The three challenges

ACME defines three ways for a client to prove it controls a domain. Each fits different operational shapes.

http-01 — easiest, port 80 required

The CA tells the client "serve a specific token at http://example.com/.well-known/acme-challenge/<token>". The CA then fetches that URL, follows redirects (HTTPS is fine), and verifies the response.

  • Pros: Trivial — most servers can serve a static file. Works for shared hosting, simple VPS.
  • Cons: Doesn't support wildcards (*.example.com). Requires port 80 to be reachable from the public internet.

dns-01 — only choice for wildcards

The CA tells the client "add a TXT record at _acme-challenge.example.com with value Y". The client (or its DNS API integration) creates the record; the CA queries DNS and verifies.

  • Pros: Doesn't need any port open on the server. Supports wildcards. Works for internal services that aren't publicly reachable.
  • Cons: Needs DNS API access. DNS propagation can be slow (most automation tools poll until the record is visible from authoritative nameservers).

tls-alpn-01 — port 80 blocked? use 443

The CA does a TLS handshake on port 443 and asks for a special ALPN protocol (acme-tls/1). The server responds with a self-signed cert containing the challenge token in an extension.

  • Pros: Only needs port 443 open. No HTTP server needed.
  • Cons: Requires careful TLS server configuration — most off-the-shelf web servers don't support tls-alpn-01 natively. Caddy, Traefik, and the simple_acme_dns libraries do.

Decision tree

Need a wildcard cert?
  YES → dns-01 (only option)
  NO  ↓

Port 80 open and reachable from the public internet?
  YES → http-01 (simplest)
  NO  ↓

Port 443 open and you control the TLS stack?
  YES → tls-alpn-01
  NO  → can't ACME this domain — use a different CA or workflow

Let's Encrypt — the dominant CA

Let's Encrypt is run by ISRG, a 501(c)(3) non-profit. As of 2024 it issues over half the public-internet TLS certs in existence. Free, automated, no exception classes — the same flow whether you're issuing one cert for a personal site or one million for a CDN edge.

Key parameters:

  • Validity period: 90 days. Renew at 60 days remaining (default for most clients).
  • Rate limits:
    • 50 certs per registered domain per week.
    • 5 duplicate certs per identical name set per week.
    • 300 new orders per account per 3 hours.
  • Staging environment: acme-staging-v02.api.letsencrypt.org — same flow but issues untrusted certs. Always test there first to avoid burning rate limits.
  • Revocation: Unlimited and free, via the same ACME API.
  • OCSP stapling: Required by Let's Encrypt as of 2025 (in lieu of CRL).

Toolchain

  • Certbot — EFF's reference client. Available on every Linux distro. Has plugins for Apache, Nginx, standalone, webroot, DNS providers (Cloudflare, Route53, etc.).
  • acme.sh — pure-shell ACME client. Tiny, runs anywhere, supports >100 DNS providers.
  • cert-manager — Kubernetes-native. CRDs for Issuer, ClusterIssuer, Certificate. Reconciliation loop handles renewal, secret updates, ingress wiring.
  • Caddy — web server with automatic HTTPS built in. One config line and Caddy handles ACME, OCSP, renewal, all transparently.
  • Traefik — same idea for reverse proxies. ACME built in.
  • lego — Go ACME library + CLI. Embeddable. The internals of cert-manager and Traefik.

For new deployments, Caddy or cert-manager are usually the right choice. Manual certbot is mostly for legacy retrofits.

Operational gotchas

  • Test in staging. Burning the production rate limit takes a week to recover from.
  • Renew at 30+ days remaining, not at expiry. ACME can fail (DNS hiccup, port 80 firewall change); you need slack.
  • Watch the rate limits per "registered domain", which means the eTLD+1. a.example.com and b.example.com count against the same example.com quota.
  • Wildcards are dns-01 only. If you can't automate DNS, you can't get wildcards from Let's Encrypt.
  • OCSP stapling matters. Without it, browsers do an OCSP lookup themselves — slower, less private. Configure your web server (ssl_stapling on for Nginx) to cache and serve OCSP responses.
  • cert-manager + ingress-nginx is the K8s stack. Annotate the Ingress with cert-manager.io/cluster-issuer: letsencrypt-prod and you're done.
  • Don't forget IPv6. http-01 challenges follow DNS — if your AAAA record is broken, the CA may try IPv6 first and fail.

Beyond Let's Encrypt

ACME isn't a Let's Encrypt monopoly. It's a public protocol implemented by:

  • ZeroSSL (free + paid tiers; covers some quirks of LE rate limits).
  • Buypass (free LE alternative, longer validity period — 180 days).
  • Google Trust Services Public CA (Google's public ACME CA).
  • stepCA (Smallstep's open-source CA — run your own ACME server for internal services).
  • AWS Certificate Manager for ACME via AWS-issued certs (limited scope).

You can configure most ACME clients to point at a different directoryUrl and use any of these. Useful for redundancy: when LE has an outage, having a secondary configured saves you.

The takeaway

If your service has a public TLS endpoint, ACME automation is the right answer in 2024. The tooling is mature, the protocol is stable, and free certs renewed by a cron job is now the baseline expectation. There's no excuse for "I forgot to renew" outages anymore.

If you're not yet using ACME, the cheapest path is:

  1. Install Caddy. Point it at your service. Done.

If you can't change the web server:

  1. apt install certbot python3-certbot-nginx.
  2. certbot --nginx -d example.com.
  3. Verify the cron timer is enabled (systemctl status certbot.timer).

Five minutes, full automation, no expiry surprises.

Tools in the wild

4 tools
  • Certbotfree tier

    EFF's official ACME client. apt/yum/brew packaged. Reasonable defaults; webroot or standalone modes.

    cli
  • acme.shfree tier

    Pure-shell ACME client — no Python, no system deps. Works on tiny embedded systems.

    cli
  • cert-managerfree tier

    Kubernetes-native cert lifecycle. Ingress annotations → Issuer → Certificate, fully automated.

    service
  • Caddyfree tier

    Web server with built-in automatic HTTPS via ACME. One config line, certificates handled.

    service