# LetsEncrypt DNS Validation Template Using Cloudflare

**URL:** https://meta.discourse.org/t/letsencrypt-dns-validation-template-using-cloudflare/313501
**Category:** Self-hosting
**Tags:** letsencrypt
**Created:** [June 25, 2024, 7:17am UTC](https://meta.discourse.org/t/letsencrypt-dns-validation-template-using-cloudflare/313501 "2024-06-25T07:17:37Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![Mark\_Wass](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/mark_wass/32/290526_2.png) [@Mark\_Wass](https://meta.discourse.org/u/Mark_Wass)
#### Post date: [June 25, 2024, 7:17am UTC](https://meta.discourse.org/t/letsencrypt-dns-validation-template-using-cloudflare/313501/1 "2024-06-25T07:17:37Z")

</div>

Hi Guys

I needed to use the DNS method for authenticating my SSL certificate ownership with Let’s Encrypt.

I took a copy of the existing `web.letsencrypt.ssl.template.yml` file found in `/var/discourse/templates/` and modified it to use the Automatic DNS API Integration method. Below is my sample template, I’d be happy if there were any suggestion on how to improve it.

I called this file `web.letsencrypt.ssl.dns.template.yml`

```plaintext
env:
  LETSENCRYPT_DIR: "/shared/letsencrypt"
  DISCOURSE_FORCE_HTTPS: true

hooks:
  after_ssl:
    - exec:
       cmd:
         - if [-z "$LETSENCRYPT_ACCOUNT_EMAIL"]; then echo "LETSENCRYPT_ACCOUNT_EMAIL ENV variable is required and has not been set."; exit 1; fi
         - /bin/bash -c "if [[! \"$LETSENCRYPT_ACCOUNT_EMAIL\" =~ ([^@]+)@([^\.]+) ]]; then echo \"LETSENCRYPT_ACCOUNT_EMAIL is not a valid email address\"; exit 1; fi"

    - exec:
       cmd:
         - cd /root && git clone --branch 3.0.7 --depth 1 https://github.com/acmesh-official/acme.sh.git && cd /root/acme.sh
         - touch /var/spool/cron/crontabs/root
         - install -d -m 0755 -g root -o root $LETSENCRYPT_DIR
         - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --install --log "${LETSENCRYPT_DIR}/acme.sh.log"
         - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --upgrade --auto-upgrade
         - cd /root/acme.sh && LE_WORKING_DIR="${LETSENCRYPT_DIR}" ./acme.sh --set-default-ca --server letsencrypt 

    - file:
       path: /etc/runit/1.d/letsencrypt
       chmod: "+x"
       contents: |
        #!/bin/bash
        
        issue_cert() {
          export CF_Token="$$ENV_LETSENCRYPT_CF_TOKEN"
          export CF_Account_ID="$$ENV_LETSENCRYPT_CF_ACCOUNT_ID"
          export CF_Zone_ID="$$ENV_LETSENCRYPT_CF_ZONE_ID"
          LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh --issue --dns $$ENV_LETSENCRYPT_DNS_PROVIDER $2 -d $$ENV_DISCOURSE_HOSTNAME --keylength $1 -w /var/www/discourse/public
        }

        cert_exists() {
          [["$(cd $$ENV_LETSENCRYPT_DIR/$$ENV_DISCOURSE_HOSTNAME$1 && openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer | grep "OK")"]]
        }

        ########################################################
        # RSA cert
        ########################################################
        issue_cert "4096"

        if ! cert_exists ""; then
          # Try to issue the cert again if something goes wrong
          issue_cert "4096" "--force"
        fi

        LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \
          --installcert \
          -d $$ENV_DISCOURSE_HOSTNAME \
          --fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer \
          --keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key \
          --reloadcmd "sv reload nginx"

        ########################################################
        # ECDSA cert
        ########################################################
        issue_cert "ec-256"

        if ! cert_exists "_ecc"; then
          # Try to issue the cert again if something goes wrong
          issue_cert "ec-256" "--force"
        fi

        LE_WORKING_DIR="${LETSENCRYPT_DIR}" $$ENV_LETSENCRYPT_DIR/acme.sh \
          --installcert --ecc \
          -d $$ENV_DISCOURSE_HOSTNAME \
          --fullchainpath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer \
          --keypath /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key \
          --reloadcmd "sv reload nginx"

        if cert_exists "" || cert_exists "_ecc"; then
          grep -q 'force_https' "/var/www/discourse/config/discourse.conf" || echo "force_https = 'true'" >> "/var/www/discourse/config/discourse.conf"
        fi

    - replace:
       filename: "/etc/nginx/conf.d/discourse.conf"
       from: /ssl_certificate.+/
       to: |
         ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.cer;
         ssl_certificate /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.cer;

    - replace:
       filename: /shared/letsencrypt/account.conf
       from: /#?ACCOUNT_EMAIL=.+/
       to: |
         ACCOUNT_EMAIL=$$ENV_LETSENCRYPT_ACCOUNT_EMAIL

    - replace:
       filename: "/etc/nginx/conf.d/discourse.conf"
       from: /ssl_certificate_key.+/
       to: |
         ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME.key;
         ssl_certificate_key /shared/ssl/$$ENV_DISCOURSE_HOSTNAME_ecc.key;

    - replace:
       filename: "/etc/nginx/conf.d/discourse.conf"
       from: /add_header.+/
       to: |
         add_header Strict-Transport-Security 'max-age=63072000';

```

There are some extra environment variables you need to add to app.yml and possibly modify if you’re not using Cloudflare as your DNS provider. All the different providers API settings are [here](https://github.com/acmesh-official/acme.sh/wiki/dnsapi)

This is what I added to my `app.yml` under the `templates` section

```plaintext
templates:
  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/web.template.yml"
  ## Uncomment the next line to enable the IPv6 listener
  #- "templates/web.ipv6.template.yml"
  - "templates/web.ratelimited.template.yml"
  ## Uncomment these two lines if you wish to add Lets Encrypt (https)
  - "templates/web.ssl.template.yml"
  - "templates/web.letsencrypt.ssl.dns.template.yml"
  #- "templates/web.letsencrypt.ssl.template.yml"

```

And further down under the `env` section.

```plaintext
## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate
  LETSENCRYPT_ACCOUNT_EMAIL: me@mydomain.com
  LETSENCRYPT_CF_TOKEN: "YOUR_TOKEN"
  LETSENCRYPT_CF_ACCOUNT_ID: "YOUR_ACCOUNT_ID"
  LETSENCRYPT_CF_ZONE_ID: "YOUR_ZONE_ID"
  LETSENCRYPT_DNS_PROVIDER: "YOUR_DNS_PROVIDER" ## i.e. dns_cf

```

After updating those files I then simply ran the command to rebuild the docker app

```plaintext
cd /var/discourse
./launcher rebuild app 

```

Once rebuilt you should then have your app running at https:// and there should be a cron job that checks daily if your certificate needs updating. If it does need updating it will get you a new certificate and install it automatically.

Hope this helps someone.

---

<div class="post-metadata">

### Author: ![tig3](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/tig3/32/126647_2.png) [@tig3](https://meta.discourse.org/u/tig3)
#### Post date: [October 17, 2024, 11:33am UTC](https://meta.discourse.org/t/letsencrypt-dns-validation-template-using-cloudflare/313501/2 "2024-10-17T11:33:03Z")

</div>

**EDIT:** I just hit the letsencrypt 5 certificate (per exact domain) per week limit.

Did you have an issue with generating `_ecc` certificate ?

Using your post the main `website.com.cer` file is being successfully generated, however the `website.com_ecc.cer` shows 0 bytes (the \_ecc.key is fine).

But both certificates are issued by the same `issue_cert` method, so for some reason the main one works and the \_ecc fails.
