# Issue + auto-renew Let's Encrypt certs via dehydrated, served back # to HAProxy as combined PEM (fullchain + key) under # /usr/local/etc/tls/haproxy/.pem. HAProxy SNI-selects on # bind *:443 ssl crt /usr/local/etc/tls/haproxy/. # # HTTP-01 only — wildcard certs (*.veza.fr etc.) require DNS-01 and # are NOT supported here. List every subdomain explicitly in # haproxy_letsencrypt_domains. # # Run from main.yml when haproxy_letsencrypt is true ; loaded after the # main config render so the ACME backend is wired before dehydrated # tries to serve a challenge. --- - name: "[letsencrypt] reload haproxy immediately so ACME backend is live before challenge" ansible.builtin.systemd: name: haproxy state: reloaded when: haproxy_config_changed | default(false) tags: [haproxy, letsencrypt] - name: "[letsencrypt] install git curl bsdmainutils" ansible.builtin.apt: name: - git - curl - bsdmainutils state: present update_cache: true cache_valid_time: 3600 tags: [haproxy, letsencrypt, packages] - name: "[letsencrypt] ensure dirs" ansible.builtin.file: path: "{{ item }}" state: directory mode: "0755" loop: - /usr/local/etc/letsencrypt - /var/www/letsencrypt - /usr/local/etc/tls/haproxy tags: [haproxy, letsencrypt] - name: "[letsencrypt] git clone dehydrated" ansible.builtin.git: repo: https://github.com/dehydrated-io/dehydrated dest: /usr/local/etc/letsencrypt/dehydrated version: master update: false tags: [haproxy, letsencrypt] - name: "[letsencrypt] render domains.txt" ansible.builtin.template: src: letsencrypt_domains.txt.j2 dest: /usr/local/etc/letsencrypt/dehydrated/domains.txt mode: "0644" tags: [haproxy, letsencrypt] - name: "[letsencrypt] render le.config" ansible.builtin.template: src: letsencrypt_le.config.j2 dest: /usr/local/etc/letsencrypt/dehydrated/le.config mode: "0644" tags: [haproxy, letsencrypt] - name: "[letsencrypt] install dehydrated_haproxy_hook.sh" ansible.builtin.copy: src: dehydrated_haproxy_hook.sh dest: /usr/local/etc/letsencrypt/dehydrated_haproxy_hook.sh mode: "0700" tags: [haproxy, letsencrypt] - name: "[letsencrypt] install http-letsencrypt.service" ansible.builtin.copy: src: http-letsencrypt.service dest: /etc/systemd/system/http-letsencrypt.service mode: "0644" notify: Reload systemd tags: [haproxy, letsencrypt] - name: "[letsencrypt] accept Let's Encrypt terms" ansible.builtin.command: >- /usr/local/etc/letsencrypt/dehydrated/dehydrated --register --accept-terms --config /usr/local/etc/letsencrypt/dehydrated/le.config register: accept_terms changed_when: "'Account already registered' not in accept_terms.stdout" tags: [haproxy, letsencrypt] - name: "[letsencrypt] generate / renew certs as needed" ansible.builtin.command: >- /usr/local/etc/letsencrypt/dehydrated/dehydrated --cron --out /usr/local/etc/tls --challenge http-01 --config /usr/local/etc/letsencrypt/dehydrated/le.config --hook /usr/local/etc/letsencrypt/dehydrated_haproxy_hook.sh register: cert_run changed_when: "'Generating private key' in cert_run.stdout or 'Renewing certificate' in cert_run.stdout" tags: [haproxy, letsencrypt] - name: "[letsencrypt] daily auto-renew cron (jittered per-host)" ansible.builtin.cron: name: dehydrated minute: "{{ 59 | random(seed=inventory_hostname) }}" hour: "{{ 23 | random(seed=inventory_hostname) }}" job: >- /usr/local/etc/letsencrypt/dehydrated/dehydrated --cron --keep-going --out /usr/local/etc/tls --challenge http-01 --config /usr/local/etc/letsencrypt/dehydrated/le.config --hook /usr/local/etc/letsencrypt/dehydrated_haproxy_hook.sh tags: [haproxy, letsencrypt]