HowTo: Certificate Renewal with Ansible


Here is an ansible file for renewing certs (issuing new should be easy to add, if wanted):

- hosts: all
    email: "admin@email"
      - { domain: 'domain.tld' }
      - { domain: 'domain2', altnames: ['test.domain2', 'www.domain2'], auto_www: false }
      - { domain: 'domain3', altnames: ['domain4'] }
      - { domain: 'domain5', email: 'otheradmin@domain5' }
    - name: Check certificate date
      stat: path=/etc/letsencrypt/live/{{ item.domain }}/fullchain.pem
      register: mtimes
      with_items: domains
    - name: Request new certificate
      command: "letsencrypt certonly --webroot --webroot-path /var/www/letsencrypt --email {{|default(email)|quote }} -d {{ item.item.domain|quote }}{% if item.item.auto_www|default(true) %} -d www.{{ item.item.domain|quote }}{% endif %}{% for altname in item.item.altnames|default([]) %} -d {{ altname|quote }}{% if item.item.auto_www|default(true) %} -d www.{{ altname|quote }}{% endif %}{% endfor %}"
      when: ansible_date_time.epoch|float - item.stat.mtime > 60*60*24*30
      register: output
      with_items: mtimes.results
      notify: Reload Nginx
    - name: Debug
      debug: var=item.stdout
      debug: var=item.stderr
      with_items: output.results
    - name: Reload Nginx
      service: name=nginx state=reloaded

This assumes a common folder for all virtual hosts, which can be achived in nginx with this config snippet:

location ~ .well-known/acme-challenge/ {
	root /var/www/letsencrypt;
	default_type text/plain;


Perhaps instead of looking at raw modified timestamp of a file, it’s better to use openssl command to get the actual notAfter value. That way you can be sure when that cert expires, regardless of file’s attributes.


May be an option, but needs good testing not to break, as there is no (official) ansible module for that.


My own script checks certs for a sufficient lifetime and checks the actual certification chain. Excerpts:

openssl x509 -checkend $((EXPIRE*60*60*24)) -in ${_cert}

where $EXPIRE is the minimum lifetime in days

for _chain in ${chains}; do
    cat /etc/ssl/cert.pem ${_chain} \
    | openssl verify -CAfile /dev/stdin ${_cert} >/dev/null 2>&1 \
    && return 0

I test with multiple chains because I include both LE certs (cross-signed and signed by ISRG Root X1) in my bundles. As long as one chain is OK, the check as a whole is OK.