Validating Ansible Playbooks Safely with Check Mode, Diff, and Assert
Learn how to safely validate Ansible playbooks using check mode, diff, assertions, and targeted recovery to preview changes, verify idempotency, and resume after failures without duplicating side effects.
12 Feb 2026, 23:01 UTC

Desired outcome
Run a playbook against target hosts to see exactly what would change, verify that the changes are correct, and then apply them with confidence. If a problem appears during the real run, you can resume the playbook from the point of failure without duplicating side effects.
Prerequisites
- Ansible core version >= 2.9 (check mode behavior is stable in this range).
- Access to an inventory file or dynamic inventory script that lists the hosts you want to test.
- SSH key or password that allows the control node to log in as a user with sufficient privileges to run the tasks (usually sudo).
- A copy of the playbook you intend to validate (e.g.,
site.yml).
Focused procedure
1. Run the playbook in check mode with diff
Execute the following command from your control node:
ansible-playbook site.yml --check --diff --limit staging
- Where to run: On the Ansible control machine.
- Permissions: The user invoking ansible-playbook must be able to SSH to the hosts in the
staginglimit. - Expected output: For each task that supports check mode, Ansible will show a \"changed\" status and, if the task modifies a file, a unified diff of the proposed changes under a
---/+++header. Tasks that do not support check mode are skipped and reported as \"SKIPPING\". - Risk: If a later task depends on a variable set by a skipped command, that task may fail or behave differently. Review the output for any \"SKIPPING\" messages and adjust the offending task (see step 3).
2. Inspect the diff output
Look for:
- Unintended modifications (e.g., extra lines, wrong permissions).
- Secrets that appear in plain text; if you see passwords or keys, consider adding
no_log: trueto the task or using Ansible Vault.
If the diff looks correct, proceed to a limited real run.
3. Adjust tasks that break check mode
For modules like command or shell that always report \"changed\", set changed_when: false when the task is read‑only, or use creates/removes to make the task idempotent. Example:
- name: Check if a config file exists
command: ls /etc/myapp/config.conf
changed_when: false
register: config_check
This prevents the task from being skipped in check mode while keeping the \"changed\" flag accurate.
4. Apply changes to a limited set of hosts
Once the check‑mode review passes, run the playbook for real on the same limited group:
ansible-playbook site.yml --limit staging
Verify that the number of \"changed\" tasks matches what was predicted in check mode.
5. Validate the result with assertions
Add an ansible.builtin.assert task after critical steps to ensure the system is in the expected state. Example:
- name: Assert that the web service is running
ansible.builtin.assert:
that:
- ansible_service_mgr == 'systemd'
- ansible_facts.services['httpd.service'].state == 'running'
fail_msg: \"Web service is not running after configuration\"
success_msg: \"Web service is running as expected\"
If the assertion fails, the playbook stops and you can examine the failure without having made further changes.
Expected checks
- Check‑mode run shows no unexpected \"SKIPPING\" messages for tasks that provide required facts.
- Diff output matches the intended configuration changes.
- Real run reports the same number of \"changed\" tasks as check mode.
- A second immediate real run reports zero \"changed\" tasks (idempotency).
- All
asserttasks pass.
Recovery options
If the real run fails mid‑playbook:
- Fix the issue (e.g., correct a template, adjust a command).
- Re‑run the playbook limiting to the hosts that failed, using
--start-at-taskor a tag to resume at the point of failure:
ansible-playbook site.yml --limit staging --start-at-task \"Configure application\"
Because tasks that reported \"changed\" in the first attempt will now report \"ok\" (assuming they are idempotent), the playbook will not duplicate side effects.
Limitations and verification
- Check mode cannot predict outcomes that depend on external state such as package repository availability or service responses.
- Some collection modules may ignore check mode entirely; consult the module documentation for your installed collection version.
- Using
--diffon tasks that render secrets will expose those secrets in the output; protect them withno_log: trueor Vault. - Behavior can vary between ansible‑core releases; pin versions in a
requirements.ymlfile for reproducibility.
To verify that your setup works as described, run the playbook twice against a test host:
ansible-playbook test.yml --check --diffand review the predicted changes.ansible-playbook test.ymlto apply them.ansible-playbook test.ymlagain and confirm that no tasks report \"changed\".
If the second run shows changes, revisit the changed_when or check_mode settings of the relevant tasks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.