Certbot with Cloudflare DNS using DNS-01 challenge in Docker

Let's Encrypt will issue you free SSL certificates, but you have to verify you control the domain, before they issue the certificates.

The 2 major ways of proving control over the domain:

  1. Create a specific page on your webserver that they can reach.
  2. Create a temporary DNS TXT record.

I went with option #2, as my web server(s) aren't exposed to the internet, and I didn't feel like leaving a hole punched in my firewall on ports 80/443, to use Certbot.

I use Cloudflare for my DNS needs, and they have an API that allows the temporary DNS TXT records to be created/deleted.

Steps

1) Create an API Token from Cloudflare:
  • Browse to https://dash.cloudflare.com/profile/api-tokens

  • Under API Tokens, select Create Token

  • Select Use template for Edit zone DNS

    • Under Zone Resources:
      • Include

      • Specific Zone

      • Select the domain we want to use for DDNS

      • This step is optional. If skipped, this API token will have permissions for all of your Cloudflare domains.

  • Under TTL, select Start/End dates, or leave untouched for no expiration of these permissions.

    Cloudflare API token creation

  • Once generated, copy the API token and treat it like a password.

  • Testing the API token, given the curl command that Cloudflare provides:

    curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
     -H "Authorization: Bearer NS4ZC3Y6MTJD0IXPIM4QJ3L8BCF-BRDYX7JZ0VQW2M" \
     -H "Content-Type:application/json"
    {"result":{"id":"amlf9mwd02v1t837dy3bhb1rlsuuhaw2c","status":"active"},"success":true,"errors":[],"messages":[{"code":10000,"message":"This API Token is valid and active","type":null}]}%
    
2) Create a credentials file on your Docker host:
mkdir cloudflare
echo 'dns_cloudflare_api_token = "NS4ZC3Y6MTJD0IXPIM4QJ3L8BCF-BRDYX7JZ0VQW2M"' > cloudflare/credentials
cat cloudflare/credentials
dns_cloudflare_api_token = "NS4ZC3Y6MTJD0IXPIM4QJ3L8BCF-BRDYX7JZ0VQW2M"
3) Create the docker-compose.yml file:
version: "2"

services:
  letsencrypt-cloudflare:
    image: certbot/dns-cloudflare

    # Dry Run
    command: certonly --non-interactive --dns-cloudflare --dns-cloudflare-credentials /opt/cloudflare/credentials --agree-tos -d test.example.com --server https://acme-v02.api.letsencrypt.org/directory --dry-run

    # Issue certificate
    # command: certonly --non-interactive --dns-cloudflare --dns-cloudflare-credentials /opt/cloudflare/credentials --agree-tos -d test.example.com --server https://acme-v02.api.letsencrypt.org/directory

    # Renew certificate
    # command: renew --non-interactive --no-self-upgrade --dns-cloudflare --dns-cloudflare-credentials /opt/cloudflare/credentials --agree-tos --server https://acme-v02.api.letsencrypt.org/directory

    volumes:
      - ./cloudflare:/opt/cloudflare
      - ./letsencrypt:/etc/letsencrypt
      - ./letsencrypt/log:/var/log/letsencrypt
4) Perform a dry-run for the domain test.example.com:
docker-compose up
Starting certbot_letsencrypt-cloudflare_1 ... done
Attaching to certbot_letsencrypt-cloudflare_1
letsencrypt-cloudflare_1  | Simulating a certificate request for test.example.com
letsencrypt-cloudflare_1  | Waiting 10 seconds for DNS changes to propagate
letsencrypt-cloudflare_1  | The dry run was successful.
letsencrypt-cloudflare_1  | Saving debug log to /var/log/letsencrypt/letsencrypt.log

Notes:

  • Uncomment/comment dry-run, issue certificate, or renew certificate lines as needed
  • Debug logs, if/when needed, are in letencrypt/logs/ on the Docker host.

References

Let's Encrypt https://letsencrypt.org/

github.com - certbot / certbot https://github.com/certbot/certbot

certbot-dns-cloudflare - documentation https://certbot-dns-cloudflare.readthedocs.io/en/stable/

dockerhub - certbot - dns cloudflare https://hub.docker.com/r/certbot/dns-cloudflare