diff --git a/acmebot/defaults/main.yml b/acmebot/defaults/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..edc0c9607e632df0bd0e3ae5bc4884bac472b012
--- /dev/null
+++ b/acmebot/defaults/main.yml
@@ -0,0 +1,223 @@
+---
+
+acmebot_account_mail: "{{ adminaddr }}"
+acmebot_after_nginx_proxy: true
+
+acmebot_settings: {}
+acmebot_default_settings:
+  log_level: "detail"
+  color_output: true
+
+  acme_directory_url: "https://acme-v02.api.letsencrypt.org/directory"
+  public_suffix_list_url: "https://publicsuffix.org/list/public_suffix_list.dat"
+  ocsp_responder_urls:
+    - "http://ocsp.int-x3.letsencrypt.org"
+  reload_zone_command: null
+  nsupdate_command: null
+  hpkp_report_uri: null
+  ct_submit_logs:
+    - "google_icarus"
+    - "google_pilot"
+
+  file_user: root
+  file_group: ssl-cert
+
+  # TODO default to both key types or single one? default to non-/custom params?
+  key_size: 4096  # null to turn off RSA certificates
+  key_curve: "secp384r1"  # null to turn off ECDSA certificates
+  key_cipher: null
+  key_passphrase: null  # null to turn off private key encryption
+  dhparam_size: 2048  # null to turn off custom dhparams
+  ecparam_curve: "secp384r1"  # null to turn off custom EC params
+
+  follower_mode: false
+  ocsp_must_staple: false  # application support isn't good enough
+  auto_rollover: true  # must be false on followers
+  pin_subdomains: false
+  verify: null  # e.g. [443]
+  services: null  # e.g. [nginx-proxy]
+
+  hpkp_days: 60
+  renewal_days: 30
+  expiration_days: 730
+  max_dns_lookup_attempts: 60
+  dns_lookup_delay: 10
+  max_domains_per_order: 100
+  max_authorization_attempts: 30
+  authorization_delay: 10
+  cert_poll_time: 30
+  max_ocsp_verify_attempts: 10
+  ocsp_verify_retry_delay: 5
+  min_run_delay: 300
+  max_run_delay: 3600
+
+# can be empty string, e.g. when using only one key type
+acmebot_key_suffixes: {}
+acmebot_default_key_suffixes:
+  rsa: ".rsa"
+  ecdsa: ".ecdsa"
+
+# format strings with: name (of privkey or cert), key_type, suffix, server
+# http_challenge uses: zone, host (without zone, "." if fqdn == zone), fqdn
+# if http_challenge is set, defaults to http-01
+#   set to null for specified certs to use dns-01 for those
+acmebot_directories: {}
+acmebot_default_directories:
+  pid: "/run"
+  log: "/var/log/acmebot"
+  resource: "/var/lib/acmebot"
+  temp: null
+
+  # TODO layout equivalent to acmetool
+  private_key: /etc/ssl/acmebot/privkey
+  backup_key: /etc/ssl/acmebot/backup_privkey
+  previous_key: null
+  full_key: /etc/ssl/acmebot/full_privkey  # maybe null to turn off
+  certificate: /etc/ssl/acmebot/cert
+  full_certificate: /etc/ssl/acmebot/full_cert  # maybe null
+  chain: /etc/ssl/acmebot/chain  # maybe null
+  param: /etc/ssl/acmebot/params  # maybe null
+  challenge: /etc/ssl/acmebot/challenges  # for dns-01 only
+  http_challenge: /var/www/acme-challenge  # maybe null
+  hpkp: /etc/ssl/acmebot/hpkp  # maybe null
+  ocsp: /etc/ssl/acmebot/ocsp  # maybe null
+  sct: "/etc/ssl/acmebot/scts/{name}/{key_type}"  # maybe null
+  update_key: /etc/ssl/acmebot/update_keys
+  archive: /etc/ssl/acmebot/archive
+
+# format strings with: name (of privkey or cert), key_type, suffix, server
+acmebot_file_names: {}
+acmebot_default_file_names:
+  log: "acmebot.log"
+
+  # TODO layout equivalent to acmetool
+  private_key: "{name}{suffix}.key"
+  backup_key: "{name}_backup{suffix}.key"
+  previous_key: "{name}_previous{suffix}.key"
+  full_key: "{name}_full{suffix}.key"
+  certificate: "{name}{suffix}.pem"
+  full_certificate: "{name}+root{suffix}.pem"
+  chain: "{name}_chain{suffix}.pem"
+  param: "{name}_param.pem"
+  challenge: "{name}"
+  hpkp: "{name}.{server}"
+  ocsp: "{name}{suffix}.ocsp"
+  sct: "{ct_log_name}.sct"
+
+# override with null
+acmebot_hpkp_headers: {}
+acmebot_default_hpkp_headers:
+  apache: "Header always set Public-Key-Pins \"{header}\"\n"
+  nginx: "add_header Public-Key-Pins \"{header}\" always;\n"
+
+acmebot_services: {}
+acmebot_default_services:
+  dovecot: "systemctl restart dovecot"
+  mysql: "systemctl reload mysql"
+  nginx: "systemctl reload nginx"
+  nginx-proxy: "systemctl reload nginx-proxy"
+  postfix: "systemctl reload postfix"
+  postgresql: "systemctl reload postgresql"
+  prosody: "systemctl restart prosody"
+
+# authorizations to maintain without certficates (e.g. for master/follower)
+acmebot_authorizations: {}
+#   <zone-name>:
+#     - <host-name>
+#     - <host-name>
+
+# when global http_challenges directory set: use null to revert back to dns-01
+# else: override dns-01 default with http-01 per domain
+acmebot_http_challenges: {}
+#   <domain-name>: <challenge-directory>
+
+# for doing DNSSEC manually, specify TSIG keys
+acmebot_zone_update_keys: {}
+
+# when using HPKP it may be beneficial to share private keys between certs
+# this dict contains multiple certificate sections per private key,
+#   all key-specific config moved up
+acmebot_private_keys: {}
+
+acmebot_certificates: {}
+#   <certificate-name>:
+#     common_name: <common-name>
+#     alt_names:
+#       <zone-name>:
+#         - "@",
+#         - <host-name>
+#     services:
+#       - <service-name>
+#     tlsa_records:
+#       <zone-name>:
+#         - <host-name>
+#         - host: <host-name>
+#           port: <port-number>
+#           usage: pkix-ee
+#           selector: spki
+#           protocol: tcp
+#           ttl: 300
+#     dhparam_size: 2048
+#     ecparam_curve: secp384r1
+#     key_types:
+#       - rsa
+#       - ecdsa
+#     key_size: 4096
+#     key_curve: secp384r1
+#     key_cipher: blowfish
+#     key_passphrase:
+#     expiration_days: 730
+#     auto_rollover: false
+#     hpkp_days: 30
+#     pin_subdomains: true
+#     hpkp_report_uri:
+#     ocsp_must_staple: false
+#     ocsp_responder_urls:
+#       - "http://ocsp.int-x3.letsencrypt.org"
+#     ct_submit_logs:
+#       - google_icarus
+#       - google_pilot
+#     verify:
+#       - 443,
+#       - port: 25
+#         hosts:
+#           - <domain-name>
+#           - <domain-name>
+#         starttls: smtp
+#         key_types:
+#           - rsa
+# 					- ecdsa
+
+# all empty per default, see README for possible hook names
+acmebot_hooks: {}
+
+# see also: https://www.certificate-transparency.org/known-logs
+acmebot_ct_logs: {}
+acmebot_default_ct_logs:
+  google_pilot:
+    url: "https://ct.googleapis.com/pilot"
+    id: "pLkJkLQYWBSHuxOizGdwCjw1mAT5G9+443fNDsgN3BA="
+  google_icarus:
+    url: "https://ct.googleapis.com/icarus"
+    id: "KTxRllTIOWW6qlD8WAfUt2+/WHopctykwwz05UVH9Hg="
+  google_rocketeer:
+    url: "https://ct.googleapis.com/rocketeer"
+    id: "7ku9t3XOYLrhQmkfq+GeZqMPfl+wctiDAMR7iXqo/cs="
+  google_skydiver:
+    url: "https://ct.googleapis.com/skydiver"
+    id: "u9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e0YU="
+  google_argon2018:
+    url: "https://ct.googleapis.com/logs/argon2018"
+    id: "pFASaQVaFVReYhGrN7wQP2KuVXakXksXFEU+GyIQaiU="
+  digicert:
+    url: "https://ct1.digicert-ct.com/log"
+    id: "VhQGmi/XwuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0="
+  symantec_ct:
+    url: "https://ct.ws.symantec.com"
+    id: "3esdK3oNT6Ygi4GtgWhwfi6OnQHVXIiNPRHEzbbsvsw="
+  symantec_vega:
+    url: "https://vega.ws.symantec.com"
+    id: "vHjh38X2PGhGSTNNoQ+hXwl5aSAJwIG08/aRfz7ZuKU="
+  cloudflare_nimbus2018:
+    url: "https://ct.cloudflare.com/logs/nimbus2018"
+    id: "23Sv7ssp7LH+yj5xbSzluaq7NveEcYPHXZ1PN7Yfv2Q="
diff --git a/acmebot/files/service-after.conf b/acmebot/files/service-after.conf
new file mode 100644
index 0000000000000000000000000000000000000000..a54ec72b702a5b82200d813310d1f8f5fc2fe5e0
--- /dev/null
+++ b/acmebot/files/service-after.conf
@@ -0,0 +1,2 @@
+[Unit]
+After=nginx-proxy.service
diff --git a/acmebot/handlers/main.yml b/acmebot/handlers/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..109aa69fe24fa659bfe116cdc86cc8a5b0ed44fb
--- /dev/null
+++ b/acmebot/handlers/main.yml
@@ -0,0 +1,7 @@
+---
+
+- name: reload systemd service files
+  systemd: daemon_reload=yes
+
+- name: update certificates
+  systemd: name=acmebot.service state=restarted
diff --git a/acmebot/tasks/main.yml b/acmebot/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5cb2fcbf697848da27409c1507037ec90f6d46bc
--- /dev/null
+++ b/acmebot/tasks/main.yml
@@ -0,0 +1,83 @@
+---
+# TODO import account info from acmetool if present
+
+- name: ensure acmebot is installed
+  apt:
+    name: acmebot
+    state: present
+
+- name: ensure we can modify the systemd unit
+  file:
+    path: /etc/systemd/system/acmebot.service.d
+    state: directory
+    owner: root
+    group: root
+    mode: '0755'
+  notify:
+    - reload systemd service files
+  when: acmebot_after_nginx_proxy
+
+- name: ensure systemd waits for proxy service
+  copy:
+    src: service-after.conf
+    dest: /etc/systemd/system/acmebot.service.d/nginx-proxy.conf
+    owner: root
+    group: root
+    mode: '0644'
+  notify:
+    - reload systemd service files
+  when: acmebot_after_nginx_proxy
+
+- name: ensure systemd does not wait for proxy service
+  file:
+    path: /etc/systemd/system/acmebot.service.d/nginx-proxy.conf
+    state: absent
+  notify:
+    - reload systemd service files
+  when: not acmebot_after_nginx_proxy
+
+- name: ensure the acmebot is configured
+  template:
+    src: acmebot.yaml.j2
+    dest: /etc/acmebot/acmebot.yaml
+    owner: root
+    group: root
+    mode: '0644'
+  notify:
+    - update certificates
+
+- name: ensure the LE root certificates are linked
+  file:
+    path: "/etc/acmebot/{{ item }}"
+    src: /etc/ssl/certs/ISRG_Root_X1.pem
+    state: link
+  with_items:
+    - root_cert.rsa.pem
+    - root_cert.ecdsa.pem
+  notify:
+    - update certificates
+
+# TODO initial run accepting TOS
+- name: check if acmebot is configured
+  command: acmetool status
+  register: acmetool_status
+  changed_when: false
+- name: initially configure acmebot
+  command: acmebot --detail
+  when: not acmetool_status.stdout is search(acmetool_endpoint)
+
+# TODO force run when cert store does not match configured certificates
+- name: test if the desired certificates are present
+  stat:
+    path: "/var/lib/acme/live/{{item.hostnames[0]}}"
+  register: live_stat
+  changed_when: not live_stat.stat.exists
+  with_items: "{{acmetool_certificates}}"
+  notify:
+    - update certificates
+
+- name: ensure certificates are updated regularly
+  systemd:
+    name: acmebot.timer
+    enabled: true
+    state: started
diff --git a/acmebot/templates/acmebot.yaml.j2 b/acmebot/templates/acmebot.yaml.j2
new file mode 100644
index 0000000000000000000000000000000000000000..b2c9564349daabc08a960d4f5a2d11428dc940ea
--- /dev/null
+++ b/acmebot/templates/acmebot.yaml.j2
@@ -0,0 +1,43 @@
+---
+
+{% set certificates = dict(certificates=acmebot_certificates) %}
+{{ certificates|to_nice_yaml }}
+
+{% set private_keys = dict(private_keys=acmebot_private_keys) %}
+{{ private_keys|to_nice_yaml }}
+
+account:
+  email: "{{ acmebot_account_mail }}"
+
+{% set settings = dict(settings=acmebot_default_settings|combine(acmebot_settings, recursive=True)) %}
+{{ settings|to_nice_yaml }}
+
+{% set directories = dict(directories=acmebot_default_directories|combine(acmebot_directories)) %}
+{{ directories|to_nice_yaml }}
+
+{% set key_type_suffixes = dict(key_type_suffixes=acmebot_default_key_suffixes|combine(acmebot_key_suffixes)) %}
+{{ key_type_suffixes|to_nice_yaml }}
+
+{% set file_names = dict(file_names=acmebot_default_file_names|combine(acmebot_file_names)) %}
+{{ file_names|to_nice_yaml }}
+
+{% set hpkp_headers = dict(hpkp_headers=acmebot_default_hpkp_headers|combine(acmebot_hpkp_headers)) %}
+{{ hpkp_headers|to_nice_yaml }}
+
+{% set services = dict(services=acmebot_default_services|combine(acmebot_services)) %}
+{{ services|to_nice_yaml }}
+
+{% set authorizations = dict(authorizations=acmebot_authorizations) %}
+{{ authorizations|to_nice_yaml }}
+
+{% set http_challenges = dict(http_challenges=acmebot_http_challenges) %}
+{{ http_challenges|to_nice_yaml }}
+
+{% set zone_update_keys = dict(zone_update_keys=acmebot_zone_update_keys) %}
+{{ zone_update_keys|to_nice_yaml }}
+
+{% set hooks = dict(hooks=acmebot_hooks) %}
+{{ hooks|to_nice_yaml }}
+
+{% set ct_logs = dict(ct_logs=acmebot_default_ct_logs|combine(acmebot_ct_logs, recursive=True)) %}
+{{ ct_logs|to_nice_yaml }}