Generate Let's Encrypt SSL Certificates with Ansible and Cloudflare
By Tony Mackay ·
This tutorial will show you how to create an Ansible playbook that makes it easy to automate the creation and renewal of Let's Encrypt SSL certificates.
What is Let’s Encrypt?
Let’s Encrypt is a non-profit certificate authority that provides SSL certificates for free. Let’s Encrypt certificates are valid for 90 days and the validation is done using HTTP or DNS which means you can automate the creation process.
This is better than the old way of email validation and having to manually apply certificates because it saves time and reduces the chances of making mistakes or forgetting to renew the certificate.
For this tutorial, we will use DNS as the method of validation by creating an Ansible task that automatically updates the Cloudflare DNS record. If you don’t use Cloudflare, you can still get an idea of how to generate the SSL from this playbook.
Create Ansible Playbook
Create a file called ssl.yml
and add the following:
- hosts: localhost
gather_facts: no
vars:
certs_path: ../certs
crt_common_name: graspingtech.com
crt_subject_alt_name:
- www.graspingtech.com
cloudflare_email: "{{ lookup('env','CF_EMAIL') }}"
cloudflare_api_token: "{{ lookup('env','CF_API_TOKEN') }}"
cloudflare_zone: "{{ lookup('env','CF_ZONE') }}"
tasks:
- name: create directory to store certs
file:
path: "{{ certs_path }}"
state: directory
- name: generate account key
openssl_privatekey:
path: "{{ certs_path }}/account-key.pem"
size: 4096
- name: generate signing key
openssl_privatekey:
path: "{{ certs_path }}/{{ crt_common_name }}.pem"
size: 4096
- name: generate csr
openssl_csr:
path: "{{ certs_path }}/{{ crt_common_name }}.csr"
privatekey_path: "{{ certs_path }}/{{ crt_common_name }}.pem"
common_name: "{{ crt_common_name }}"
subject_alt_name: "DNS:{{ crt_subject_alt_name | join(',DNS:') }}"
- name: create acme challenge
acme_certificate:
acme_version: 2
terms_agreed: yes
account_key_src: "{{ certs_path }}/account-key.pem"
src: "{{ certs_path }}/{{ crt_common_name }}.csr"
cert: "{{ certs_path }}/{{ crt_common_name }}.crt"
challenge: dns-01
acme_directory: https://acme-v02.api.letsencrypt.org/directory
remaining_days: 60
register: challenge
- name: create cloudflare TXT records
cloudflare_dns:
account_api_token: "{{ cloudflare_api_token }}"
account_email: "{{ cloudflare_email }}"
zone: "{{ cloudflare_zone }}"
record: "{{ challenge.challenge_data[item]['dns-01'].record }}"
type: TXT
value: "{{ challenge.challenge_data[item]['dns-01'].resource_value }}"
solo: true
state: present
with_items: "{{ [crt_common_name] + crt_subject_alt_name }}"
when: challenge is changed
- name: validate acme challenge
acme_certificate:
acme_version: 2
account_key_src: "{{ certs_path }}/account-key.pem"
src: "{{ certs_path }}/{{ crt_common_name }}.csr"
cert: "{{ certs_path }}/{{ crt_common_name }}.crt"
fullchain: "{{ certs_path }}/{{ crt_common_name }}-fullchain.crt"
chain: "{{ certs_path }}/{{ crt_common_name }}-intermediate.crt"
challenge: dns-01
acme_directory: https://acme-v02.api.letsencrypt.org/directory
remaining_days: 60
data: "{{ challenge }}"
when: challenge is changed
- name: delete cloudflare TXT record
cloudflare_dns:
account_api_token: "{{ cloudflare_api_token }}"
account_email: "{{ cloudflare_email }}"
zone: "{{ cloudflare_zone }}"
record: "{{ challenge.challenge_data[item]['dns-01'].record }}"
type: TXT
state: absent
with_items: "{{ [crt_common_name] + crt_subject_alt_name }}"
when: challenge is changed
Assign your domain name to the crt_common_name
variable and any extra subdomains you want to include in the certificate to the crt_subject_alt_name
variable.
Since the playbook is using Cloudflare DNS to validate the Let’s Encrypt certificate, you will also need to assign your Cloudflare API credentials to the cloudflare_*
variables. You can either assign them directly in the playbook or pass them in via environment variables by running the commands below before running the playbook:
export CF_EMAIL=<your_cloudflare_email>
export CF_API_TOKEN=<your_cloudflare_api_token>
export CF_ZONE=<your_cloudflare_api_zone>
Run Playbook
Run the Ansible playbook with the following command:
ansible-playbook ssl.yml
After running the playbook. The playbook will create a new folder called certs
and the generated keys and certificates will be stored inside it.

certs folder

Conclusion
That’s all there is to it. After running the playbook, you should have a certs
folder containing your SSL certificate. Now you can use another playbook to deploy and install the certificate to your web or application server.
If you ever need to renew the certificate, you can run this playbook again and the new certificate will replace the existing one.
Read Next
- The Best Books to Learn Virtualization, Linux and Automation
- Recommended Tools and Software for System Administrators
- Automate Let's Encrypt Certificate Install on NGINX
- Using Ansible to configure NGINX on Ubuntu and host a static website