diff --git a/.github/workflows/molecule.yml b/.github/workflows/molecule.yml
new file mode 100644
index 0000000..d955dfe
--- /dev/null
+++ b/.github/workflows/molecule.yml
@@ -0,0 +1,49 @@
+---
+  on:
+    # Trigger the workflow on push or pull request,
+    # but only for the main branch
+    push:
+      branches:
+        - master
+    pull_request:
+      branches:
+        - master
+  jobs:
+    lint:
+      runs-on: ubuntu-latest
+      steps:
+        - name: checkout
+          uses: actions/checkout@v2
+          with:
+            path: "${{ github.repository }}"
+        - name: molecule
+          uses: robertdebock/molecule-action@2.6.3
+          with:
+            command: lint
+    test:
+      needs:
+        - lint
+      runs-on: ubuntu-latest
+      strategy:
+        matrix:
+          image:
+            - geerlingguy/docker-ubuntu2404-ansible:latest
+            - geerlingguy/docker-ubuntu2204-ansible:latest
+            - geerlingguy/docker-ubuntu2004-ansible:latest
+            - geerlingguy/docker-ubuntu1804-ansible:latest
+            - geerlingguy/docker-ubuntu1604-ansible:latest
+            - geerlingguy/docker-centos8-ansible:latest
+            - geerlingguy/docker-centos7-ansible:latest
+      steps:
+        - name: checkout
+          uses: actions/checkout@v2
+          with:
+            path: "${{ github.repository }}"
+        - name: molecule
+          uses: robertdebock/molecule-action@6.0.1
+          with:
+            image: "${{ matrix.image }}"
+            options: parallel
+          env:
+            MOLECULE_DOCKER_IMAGE: "${{ matrix.image }}"
+  
\ No newline at end of file
diff --git a/README.md b/README.md
index 3c8ddbc..e701441 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,10 @@ After this role is installed using its default configuration, you won't need to
 
   String, defaults to `/var/www/letsencrypt`. The physical directory for certbot to create acme challenge files in. Each virtual host's `/.well-known/acme-challenge` location maps to `{{ letsencrypt_webroot }}/.well-known/acme-challenge`.
 
+- **letsencrypt_staging**
+
+  Boolean (`false`) by default. If set to `true`, certbot will use the LetsEncrypt staging server instead of the production server. This is useful for testing, as the staging server has much higher rate limits.
+
 ## Dependencies
 
 * [acromedia.nginx](https://github.com/AcroMedia/ansible-role-nginx) when `letsencrypt_webserver: nginx` (the default), or
diff --git a/molecule/default/INSTALL.rst b/molecule/default/INSTALL.rst
new file mode 100644
index 0000000..6a44bde
--- /dev/null
+++ b/molecule/default/INSTALL.rst
@@ -0,0 +1,22 @@
+*******
+Docker driver installation guide
+*******
+
+Requirements
+============
+
+* Docker Engine
+
+Install
+=======
+
+Please refer to the `Virtual environment`_ documentation for installation best
+practices. If not using a virtual environment, please consider passing the
+widely recommended `'--user' flag`_ when invoking ``pip``.
+
+.. _Virtual environment: https://virtualenv.pypa.io/en/latest/
+.. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site
+
+.. code-block:: bash
+
+    $ pip install 'molecule[docker]'
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
new file mode 100644
index 0000000..7b601ac
--- /dev/null
+++ b/molecule/default/converge.yml
@@ -0,0 +1,37 @@
+---
+- name: Converge
+  hosts: all
+  become: true
+  vars:
+    default_mail_recipient: admin@example.com
+    letsencrypt_staging: true
+
+  pre_tasks:
+    - name: Install NGINX
+      package:
+        name: nginx
+        state: present
+        update_cache: yes
+
+    - name: Install snapd
+      package:
+        name: snapd
+        state: present
+
+    - name: Ensure snapd is running
+      service:
+        name: snapd
+        state: started
+        enabled: yes
+
+    - name: Ensure udev is running
+      service:
+        name: udev
+        state: started
+        enabled: yes
+
+    - name: Update snap
+      shell: snap install core; snap refresh core
+
+  roles:
+    - role: ansible-role-letsencrypt
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
new file mode 100644
index 0000000..7f0f75c
--- /dev/null
+++ b/molecule/default/molecule.yml
@@ -0,0 +1,19 @@
+---
+role_name_check: 1
+dependency:
+  name: galaxy
+driver:
+  name: docker
+platforms:
+  - name: instance
+    image: "geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2004}-ansible:latest"
+    command: ${MOLECULE_DOCKER_COMMAND:-""}
+    cgroupns_mode: host
+    volumes:
+      - /sys/fs/cgroup:/sys/fs/cgroup:rw
+    privileged: true
+    pre_build_image: true
+provisioner:
+  name: ansible
+  playbooks:
+    converge: ${MOLECULE_PLAYBOOK:-converge.yml}
diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml
new file mode 100644
index 0000000..0a10c4e
--- /dev/null
+++ b/molecule/default/verify.yml
@@ -0,0 +1,25 @@
+---
+- name: Check whether letsencrypt ran properly
+  hosts: all
+  become: yes
+
+  tasks:
+    - include_vars: "vars/{{ ansible_os_family }}.yml"
+
+    - name: Check if certbot was installed
+      command: certbot --version
+      register: certbot_installed
+
+    - name: Assert that certbot --version was successful
+      assert:
+        that: certbot_installed.rc == 0
+        msg: "certbot --version failed"
+
+    # Check that the acme-challenge directory exists
+    - name: Check if acme-challenge directory exists
+      stat:
+        path: "{{ letsencrypt_webroot }}/.well-known/acme-challenge"
+
+    - name: Stat default site SSL cert
+      shell: "test -e /etc/letsencrypt/live/{{ default_site_fqdn }}"
+        
diff --git a/tasks/main.yml b/tasks/main.yml
index 05f46f7..fc6abb2 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -100,6 +100,7 @@
           --webroot
           --webroot-path {{ letsencrypt_webroot }}
           --domains {{ default_site_fqdn }}
+          {{ letsencrypt_staging | ternary('--test-cert', '') }}
 
     - name: Create a cert for the default site (can take some time)
       shell: >