MyOwnPKI documentation Source on GitHub

Traefik and Caddy as ACME clients

When to use this

Use this when Traefik or Caddy already terminates TLS for your internal services and should get those certificates from your own CA instead of Let’s Encrypt. Both are ACME clients: once they know the CA’s address and trust its root, they obtain and renew certificates by themselves.

This page separates two kinds of statements. Documented means the operations guide (§10.6) gives exactly this setting. General behavior means how Traefik or Caddy normally work, written here so that the example is complete; check it against the documentation of your version. The project’s tests (CI) run lego, certbot and acme.sh against the server; Traefik and Caddy as ACME clients are documented but were not tested (release notes). To put Traefik or Caddy in front of MyOwnPKI, see Reverse proxies.

Before you start

Caddy

  1. Documented. Set the CA and its root in the global options of the Caddyfile:

    {
        acme_ca https://pki.homelab.lan/acme/directory
        acme_ca_root /etc/caddy/homelab-root.pem
    }
    
  2. General behavior. A site block with the host name is enough; Caddy asks for the certificate itself, serves port 80 for the challenge and redirects to HTTPS:

    nas.homelab.lan {
        reverse_proxy 192.0.2.20:8080
    }
    
  3. Check the file and reload Caddy:

    caddy validate --config /etc/caddy/Caddyfile
    
    systemctl reload caddy
    
  4. Renewal. Documented: Caddy supports ARI, so it asks the server when to renew. The window opens at about two thirds of the lifetime, and a revoked certificate gets a window in the past, so Caddy renews it at once. Nothing to set up.

Traefik

  1. Documented. The static configuration defines a resolver with the CA’s address and the HTTP challenge on the entry point that listens on port 80:

    certificatesResolvers:
      homelab:
        acme:
          caServer: https://pki.homelab.lan/acme/directory
          email: admin@homelab.lan
          storage: /etc/traefik/acme-homelab.json
          httpChallenge:
            entryPoint: web
    
  2. Documented. Traefik reads the root from the variable LEGO_CA_CERTIFICATES in its own environment (Traefik uses the lego library for ACME). With systemd, put it into a drop-in (systemctl edit traefik):

    [Service]
    Environment=LEGO_CA_CERTIFICATES=/etc/traefik/homelab-root.pem
    

    In a container, pass it with -e or environment: and mount the PEM file into the container.

  3. General behavior. A router asks for a certificate through the resolver. In the dynamic configuration:

    http:
      routers:
        nas:
          rule: "Host(`nas.homelab.lan`)"
          entryPoints: [websecure]
          service: nas
          tls:
            certResolver: homelab
      services:
        nas:
          loadBalancer:
            servers:
              - url: "http://192.0.2.20:8080"
    
  4. Restart Traefik so that it reads the environment, then watch its log for the first order:

    systemctl restart traefik
    
    journalctl -u traefik -n 50 --no-pager
    
  5. Renewal is Traefik’s own: it renews certificates before they expire and keeps them in the storage file. The operations guide does not document its timing or ARI for Traefik. Watch the expiry in the UI (Expiring soon on the dashboard) until you have seen one renewal.

Both tools can also use an EAB key when the domain asks for approval. Create it in ACME → EAB keys; the option names are in the documentation of each tool and are not in the operations guide. Without a key the first order waits until an admin approves the account in ACME → Accounts.

Check it worked

curl --cacert homelab-root.pem -sS -o /dev/null -w '%{http_code}\n' https://nas.homelab.lan/
openssl s_client -connect nas.homelab.lan:443 -servername nas.homelab.lan -CAfile homelab-root.pem </dev/null 2>/dev/null | openssl x509 -noout -issuer -serial -enddate

The issuer is your CA. In the UI, ACME → Orders lists the order as valid with a link to the certificate.

When it fails

What you see Usually See
x509: certificate signed by unknown authority in the Traefik log LEGO_CA_CERTIFICATES is not in Traefik’s environment, or points to the wrong file Step 2 of Traefik
The same kind of error in the Caddy log acme_ca_root is missing or unreadable by the Caddy user Step 1 of Caddy
rejectedIdentifier: no domain covers … No domain covers the name ACME → Domains
unauthorized: account … waits for approval Approval level approved accounts ACME → Accounts, or an EAB key
connection: … could not be reached Port 80 of the proxy is closed for the PKI server, or the name resolves elsewhere Split DNS
incorrectResponse: … answered with status 404 The challenge request did not reach Caddy or Traefik, for example a load balancer in front Pass /.well-known/acme-challenge/ through

The complete table is §10.9; more in Troubleshooting.

Next steps