> ## Documentation Index
> Fetch the complete documentation index at: https://certplane.kippel.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Issue certificates with Let's Encrypt and DNS-01

> Configure Certplane's broker to obtain publicly-trusted certificates from Let's Encrypt using the dns-01 ACME challenge with Cloudflare.

This guide walks you through configuring the Certplane broker to obtain publicly-trusted certificates from Let's Encrypt using the dns-01 challenge. The dns-01 challenge proves domain ownership by creating a DNS TXT record — it works even for servers not reachable from the internet, and is required for wildcard certificates.

<Warning>
  Let's Encrypt enforces strict [rate limits](https://letsencrypt.org/docs/rate-limits/) on its production environment. Always validate your setup against the staging environment before switching to production.
</Warning>

<Steps>
  <Step title="Get your Cloudflare API token">
    In the [Cloudflare dashboard](https://dash.cloudflare.com/profile/api-tokens), create an API token with **Zone → DNS → Edit** permission scoped to the zone (or zones) you want Certplane to manage. Do not use a Global API Key — a scoped token limits the blast radius if the credential is ever exposed.

    Once you have the token, note its value. You'll store it in the next step.
  </Step>

  <Step title="Store the token">
    The simplest approach is the `env` secrets provider. Export the token as an environment variable before starting the broker:

    ```bash theme={null}
    export CLOUDFLARE_DNS_API_TOKEN="your-token-here"
    ```

    With `secrets.provider: env` the broker reads the environment variable whose name appears in `acme.credentials`. To load credentials from a file or from Vault/OpenBao instead, see [Secrets providers](/configuration/secrets) and the [Vault and OpenBao secrets](/guides/vault-secrets) guide.

    <Tip>
      Generate your ACME account key before continuing. Certplane reads this key to sign ACME requests — it is not a certificate, and you should keep it in a secure, backed-up location:

      ```bash theme={null}
      openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out /etc/certplane/acme/account.key
      ```
    </Tip>
  </Step>

  <Step title="Configure the broker">
    Edit your `broker.yml` to point the `issuer` block at the Let's Encrypt **staging** directory and configure the Cloudflare DNS provider. Start with staging so you can confirm everything works without burning production rate limits.

    ```yaml theme={null}
    issuer:
      provider: acme
      acme:
        directory_url: https://acme-staging-v02.api.letsencrypt.org/directory
        account_email: admin@example.com
        account_key: /etc/certplane/acme/account.key
        dns_provider: cloudflare

    secrets:
      provider: env
    ```

    The `account_key` must be the path to the EC private key you generated above. The `account_email` is the address Let's Encrypt uses to send expiry warnings; it does not need to match any DNS record.
  </Step>

  <Step title="Create a wildcard profile in policy.yml">
    Add a profile that requests a wildcard certificate using the dns-01 challenge. The `credentials` value is the name of the secret as interpreted by your configured secrets provider — for the `env` provider, this is the environment variable name.

    ```yaml theme={null}
    profiles:
      public_wildcard:
        type: wildcard
        dns_names:
          - "*.example.com"
        acme:
          challenge: dns-01
          credentials: CLOUDFLARE_DNS_API_TOKEN
        renew_before: 720h
    ```

    Then allow the relevant host to request this profile:

    ```yaml theme={null}
    hosts:
      web01:
        identity: web01.internal.example.com
        profiles:
          - public_wildcard
    ```

    <Note>
      Let's Encrypt ignores the `ttl` field at issuance time and always issues certificates valid for **90 days**. The `renew_before` value controls when Certplane proactively renews — the default of `720h` (30 days) is a sensible starting point.
    </Note>
  </Step>

  <Step title="Test with staging, then switch to production">
    Restart the broker and trigger a certificate request from an agent. Staging certificates are signed by a fake CA that browsers do not trust, but they are structurally identical to production ones — any configuration errors will surface here.

    Once the staging certificate is issued successfully, switch the broker to the production directory:

    ```yaml theme={null}
    issuer:
      provider: acme
      acme:
        directory_url: https://acme-v02.api.letsencrypt.org/directory
        account_email: admin@example.com
        account_key: /etc/certplane/acme/account.key
        dns_provider: cloudflare
    ```

    Restart the broker. The next renewal cycle (or a forced re-request) will obtain a production certificate trusted by all major browsers and operating systems.
  </Step>
</Steps>
