AnsibleXXV · Check Mode, Diff and Static ValidationCheck mode, diff and static validation
How check mode works
What you'll learn
- Predict whether a task simulates from the CLI flag and the check_mode keyword together
- Use check_mode true to force simulation and check_mode false to force a real run
- Explain why ansible_check_mode can be False in a task that is simulating
- State what a --check run does on the managed node despite changing nothing
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
Whether a given task simulates or executes is decided by two independent controls, and a third thing that only looks like it is one of them.
| Control | Scope | Effect |
|---|---|---|
--check / -C | The whole invocation | Every task simulates, unless a keyword says otherwise |
check_mode: true / false | Play, block or task | Overrides the flag, in either direction, for what it covers |
ansible_check_mode | Read-only variable | Reports whether --check was passed — not whether this task is simulating |
The next lesson argues about what a clean --check run does and does not
prove. This one is about getting the mechanics exactly right, because
half of the misplaced confidence in check mode comes from a play whose
author believed a task was simulating when it was not, or the reverse.
The four combinations
- name: check_mode combinations
hosts: webservers
tasks:
- name: An ordinary task
ansible.builtin.command: /usr/local/bin/probe
- name: A read-only probe that must run during a check
ansible.builtin.command: /usr/local/bin/app --version
register: app_version
changed_when: false
check_mode: false
- name: A task that never runs for real
ansible.builtin.command: /usr/local/bin/destroy-everything
check_mode: true
| Task | Without --check | With --check |
|---|---|---|
| Ordinary | Executes | Simulates |
check_mode: false | Executes | Executes |
check_mode: true | Simulates | Simulates |
check_mode: false is the one to be careful with — it is a task that
runs for real during a dry run. check_mode: true is the safer of the
two, and both directions have legitimate uses.
check_mode: false — the read-only probe
The problem it solves appears the moment a play branches on something it
measured. Two identical probes, one with the keyword and one without,
during a --check run:
$ ansible-playbook -i localhost, probe.yml --checkTASK [A read-only probe that must run even during a dry run] *******************
ok: [localhost]
TASK [The same probe without check_mode false] *********************************
skipping: [localhost]
TASK [What the two probes returned] ********************************************
ok: [localhost] => {
"msg": [
"with check_mode false: 4.2.1",
"without it: "
]
}The keyword makes the probe run, and the registered value is real:
- name: Read the currently installed version
ansible.builtin.command: /usr/local/bin/app --version
register: app_version
changed_when: false
check_mode: false
changed_when: false belongs there too. The task genuinely changes
nothing, so it should not report changed in a real run either — and the
two keywords together are the signature of a correctly written probe.
check_mode: true — forcing simulation
The reverse keyword pins a task to simulation whether or not --check
was passed. Its honest use is narrow: a task nobody should run yet, kept
in the play so it is reviewed and versioned, or the destructive half of a
play during a rehearsal.
$ ansible-playbook -i inv.ini deploy.ymlTASK [A task that never runs for real, even without --check] *******************
skipping: [localhost]
TASK [What the forced-simulation task returned] ********************************
ok: [localhost] => {
"msg": {
"changed": false,
"cmd": ["/bin/echo", "would-have-run"],
"failed": false,
"msg": "Command would have run if not in check mode",
"rc": 0,
"skipped": true,
"stdout": ""
}
}It applies at block and play level too, which is how you rehearse the dangerous half of a play while the preparation runs for real:
- name: Prepare for the migration
hosts: dbservers
tasks:
- name: Take a backup # runs for real
ansible.builtin.command: /usr/local/bin/backup.sh
args:
creates: '/backups/{{ inventory_hostname }}-{{ run_id }}.dump'
- name: The migration itself # simulated until we mean it
block:
- name: Apply the schema change
community.postgresql.postgresql_query:
db: app
query: "{{ lookup('file', 'migration.sql') }}"
check_mode: true
Where ansible_check_mode disagrees with reality
ansible_check_mode is a magic variable, and the documented pattern is
to guard on it:
- name: Send the deployment notification
ansible.builtin.uri:
url: 'https://chat.example.com/api/notify'
method: POST
body_format: json
body:
text: 'Deployment complete on {{ inventory_hostname }}'
when: not ansible_check_mode
That is correct and useful — a dry run should not page the team. What it is not is a reliable statement about whether the surrounding task is simulating.
$ ansible-playbook -i localhost, forced.ymlTASK [A command that will never run] *******************************************
skipping: [localhost]
TASK [Report] ******************************************************************
ok: [localhost] => {
"msg": "skipped=True check_mode=False"
}The task simulated. ansible_check_mode is False.
And the mirror image, with the flag on the command line and the keyword turning one task off:
$ ansible-playbook -i localhost, probe.yml --checkTASK [A task forced to run for real during a check run] ************************
ok: [localhost] => {
"msg": "inside check_mode false, ansible_check_mode is True"
}Stated plainly: ansible_check_mode reports the command-line flag, not
the effective mode of the task reading it. Use it for “was this
invocation a dry run” — which is what a notification guard wants. Do not
use it to reason about whether a particular task executed.
Knowledge check
Knowledge check · 4 questions
Q1. A play carries check_mode: true and is run without --check. Inside it, a task reads ansible_check_mode. What does it get?
Q2. A command probe is skipped during a --check run because it has no check-mode support. Which of these are true of the registered variable? Select all that apply.
Q3. A --check run connects to every targeted host and executes module code there, with become if the task has it.
Q4. Which task is an appropriate place for check_mode: false?
Passing score: 75%. Answers are checked in this browser.