diff --git a/filter_plugins/acmedns_to_lego.py b/filter_plugins/acmedns_to_lego.py
new file mode 100644
index 0000000..76a24cd
--- /dev/null
+++ b/filter_plugins/acmedns_to_lego.py
@@ -0,0 +1,18 @@
+class FilterModule(object):
+    def filters(self):
+        return {
+            'acmedns_to_lego': self.acmedns_to_lego,
+        }
+
+    def acmedns_to_lego(self, acmedns_registered):
+        result = {}
+        for (key, value) in acmedns_registered.items():
+            result[key] = {
+                "fulldomain": value["subd"] + "." + value["host"],
+                "subdomain": value["subd"],
+                "username": value["user"],
+                "password": value["pass"],
+                "server_url": "https://" + value["host"]
+            }
+
+        return result
diff --git a/node002.yml b/node002.yml
index 6d4ddc3..476c5c3 100644
--- a/node002.yml
+++ b/node002.yml
@@ -6,6 +6,8 @@
       tags: [always]
     - role: backup
       tags: [backup]
+    - role: lego
+      tags: [lego, certificates]
     - role: caddy
       tags: [caddy, reverse-proxy, webserver]
       vars:
diff --git a/roles/lego/files/hook.sh b/roles/lego/files/hook.sh
new file mode 100644
index 0000000..b060634
--- /dev/null
+++ b/roles/lego/files/hook.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+cp -f "$LEGO_CERT_PATH" /certificates
+cp -f "$LEGO_CERT_KEY_PATH" /certificates
+
+exit 33 # special exit code to signal that the certificate has been updated
diff --git a/roles/lego/files/lego.sh b/roles/lego/files/lego.sh
new file mode 100755
index 0000000..f6a4a04
--- /dev/null
+++ b/roles/lego/files/lego.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env sh
+
+domain="$1"
+action="${2:-renew}"
+
+docker compose run --rm app \
+	--domains "$domain" \
+	"$action" \
+	"--$action-hook" "/config/hook.sh"
+
+if [ "$?" = "33" ] && [ -x "./lego.d/$domain" ];
+then
+	echo "Running hook for $domain"
+	"./lego.d/$domain"
+fi
diff --git a/roles/lego/files/lego@.timer b/roles/lego/files/lego@.timer
new file mode 100644
index 0000000..284347f
--- /dev/null
+++ b/roles/lego/files/lego@.timer
@@ -0,0 +1,10 @@
+[Unit]
+Description=Renew certificates
+
+[Timer]
+Persistent=true
+OnCalendar=*-*-* 01:15:00
+RandomizedDelaySec=2h
+
+[Install]
+WantedBy=timers.target
diff --git a/roles/lego/files/node002/db.serguzim.me b/roles/lego/files/node002/db.serguzim.me
new file mode 100755
index 0000000..09602b9
--- /dev/null
+++ b/roles/lego/files/node002/db.serguzim.me
@@ -0,0 +1,16 @@
+#!/usr/bin/env sh
+
+domain="db.serguzim.me"
+
+docker compose run --rm app "$1" "$domain"
+
+_install() {
+  install --owner=postgres --group=postgres --mode=600 \
+	  "/opt/services/_certificates/$domain.$1" \
+	  "/var/lib/postgresql/server.$1"
+}
+
+_install crt
+_install key
+
+sudo -u postgres pg_ctl -D /var/lib/postgres/data/ reload
diff --git a/roles/lego/files/node002/registry.serguzim.me b/roles/lego/files/node002/registry.serguzim.me
new file mode 100755
index 0000000..09e444c
--- /dev/null
+++ b/roles/lego/files/node002/registry.serguzim.me
@@ -0,0 +1,17 @@
+#!/usr/bin/env sh
+
+domain="registry.serguzim.me"
+
+docker compose run --rm app "$1" "$domain"
+
+_install() {
+  install --owner=root --group=root --mode=600 \
+	  "/opt/services/_certificates/$domain.$1" \
+	  "/opt/services/harbor/server.$1"
+}
+
+_install crt
+_install key
+
+export HARBOR_BUNDLE_DIR=/opt/services/harbor
+$HARBOR_BUNDLE_DIR/data/install.sh
diff --git a/roles/lego/tasks/config.yml b/roles/lego/tasks/config.yml
new file mode 100644
index 0000000..266efcb
--- /dev/null
+++ b/roles/lego/tasks/config.yml
@@ -0,0 +1,19 @@
+---
+- name: Set config path
+  ansible.builtin.set_fact:
+    config_path: "{{ (service_path, 'config') | path_join }}"
+- name: Create config directory
+  ansible.builtin.file:
+    path: "{{ config_path }}"
+    state: directory
+    mode: "0755"
+- name: Copy the acme-dns-accounts
+  ansible.builtin.template:
+    src: "json.j2"
+    dest: "{{ (config_path, 'acme-dns-accounts.json') | path_join }}"
+    mode: "0644"
+- name: Copy the hook script
+  ansible.builtin.copy:
+    src: "hook.sh"
+    dest: "{{ (config_path, 'hook.sh') | path_join }}"
+    mode: "0755"
diff --git a/roles/lego/tasks/lego.d.yml b/roles/lego/tasks/lego.d.yml
new file mode 100644
index 0000000..04acb4b
--- /dev/null
+++ b/roles/lego/tasks/lego.d.yml
@@ -0,0 +1,16 @@
+---
+- name: Set lego.d path
+  ansible.builtin.set_fact:
+    lego_d_path: "{{ (service_path, 'lego.d') | path_join }}"
+- name: Create lego.d directory
+  ansible.builtin.file:
+    path: "{{ lego_d_path }}"
+    state: directory
+    mode: "0755"
+- name: Copy the additional lego scripts
+  ansible.builtin.copy:
+    src: "{{ item }}"
+    dest: "{{ lego_d_path }}"
+    mode: "0755"
+  with_fileglob:
+    - "{{ ansible_facts.hostname }}/*"
diff --git a/roles/lego/tasks/main.yml b/roles/lego/tasks/main.yml
new file mode 100644
index 0000000..3dc6de1
--- /dev/null
+++ b/roles/lego/tasks/main.yml
@@ -0,0 +1,35 @@
+---
+- name: Set common facts
+  ansible.builtin.import_tasks: tasks/set-default-facts.yml
+
+- name: Deploy {{ svc.name }}
+  vars:
+    svc: "{{ lego_svc }}"
+    env: "{{ lego_env }}"
+    json: "{{ vault_acmedns_registered | acmedns_to_lego }}"
+    compose: "{{ lego_compose }}"
+  block:
+    - name: Import prepare tasks for common service
+      ansible.builtin.import_tasks: tasks/prepare-common-service.yml
+
+    - name: Create _certificates directory
+      ansible.builtin.file:
+        path: "{{ certificates_path }}"
+        state: directory
+        mode: "0755"
+
+    - name: Import tasks specific to the config directory
+      ansible.builtin.import_tasks: config.yml
+    - name: Import tasks specific to lego.d
+      ansible.builtin.import_tasks: lego.d.yml
+    - name: Import tasks specific to systemd
+      ansible.builtin.import_tasks: systemd.yml
+
+    - name: Copy the run script
+      ansible.builtin.copy:
+        src: "lego.sh"
+        dest: "{{ (service_path, 'lego.sh') | path_join }}"
+        mode: "0755"
+
+    - name: Import tasks create a service.env file
+      ansible.builtin.import_tasks: tasks/steps/template-service-env.yml
diff --git a/roles/lego/tasks/systemd.yml b/roles/lego/tasks/systemd.yml
new file mode 100644
index 0000000..21e99bf
--- /dev/null
+++ b/roles/lego/tasks/systemd.yml
@@ -0,0 +1,23 @@
+---
+- name: Copy the system service
+  ansible.builtin.template:
+    src: lego@.service.j2
+    dest: /etc/systemd/system/lego@.service
+    mode: "0644"
+  become: true
+- name: Copy the system timer
+  ansible.builtin.copy:
+    src: lego@.timer
+    dest: /etc/systemd/system/lego@.timer
+    mode: "0644"
+  become: true
+- name: Enable the system timer for {{ item }}
+  ansible.builtin.systemd_service:
+    name: lego@{{ item }}.timer
+    state: started
+    enabled: true
+    daemon_reload: true
+  loop:
+    - db.serguzim.me
+    - registry.serguzim.me
+  become: true
diff --git a/roles/lego/templates/lego@.service.j2 b/roles/lego/templates/lego@.service.j2
new file mode 100644
index 0000000..4b310f2
--- /dev/null
+++ b/roles/lego/templates/lego@.service.j2
@@ -0,0 +1,4 @@
+[Service]
+Type=oneshot
+ExecStart={{ service_path }}/lego.sh %i
+WorkingDirectory={{ service_path }}
diff --git a/roles/lego/vars/main.yml b/roles/lego/vars/main.yml
new file mode 100644
index 0000000..460fb79
--- /dev/null
+++ b/roles/lego/vars/main.yml
@@ -0,0 +1,31 @@
+---
+lego_svc:
+  name: lego
+
+lego_env:
+  ACME_DNS_API_BASE: https://{{ acme_dns.host }}
+  ACME_DNS_STORAGE_PATH: /config/acme-dns-accounts.json
+
+  LEGO_EMAIL: "{{ admin_email }}"
+  LEGO_PATH: /data
+
+lego_compose:
+  watchtower: false
+  network: false
+  image: goacme/lego
+  volumes:
+    - ./config:/config:ro
+    - "{{ certificates_path }}:/certificates"
+    - data:/data
+  file:
+    services:
+      app:
+        restart: never
+        network_mode: "host"
+        entrypoint:
+          - /lego
+          - --accept-tos
+          - --email={{ admin_email }}
+          - --dns=acme-dns
+    volumes:
+      data: