Ansible check_mode: Dry Run & Test Playbooks Without Making Changes
By Luca Berton · Published 2026-04-03 · Category: installation
How to use Ansible check_mode for dry run testing. Run playbooks safely without changes, use --check --diff, test idempotency. Practical YAML playbook examples.
Ansible check mode (--check) lets you preview what changes a playbook would make without actually applying them. It's a dry run — essential for testing playbooks safely in production.
Basic Usage
# Run playbook in check mode
ansible-playbook site.yml --check
# Check mode with diff output
ansible-playbook site.yml --check --diffSee also: Ansible vs GitHub Actions: Key Differences & When to Use Each (2026)
How Check Mode Works
In check mode, Ansible:
- Connects to remote hosts normally
- Evaluates all conditions and variables
- Reports what WOULD change — without executing changes
- Shows "changed" for tasks that would modify the system
Force Check Mode per Task
- name: Always run in check mode (even during real runs)
ansible.builtin.command: /opt/validate.sh
check_mode: true
- name: Never run in check mode (skip during dry runs)
ansible.builtin.command: /opt/setup-prereqs.sh
check_mode: falseSee also: New CI Requirement for Ansible Collections: Testing Against Devel Branch (2026)
Use check_mode Variable in Conditions
- name: Show what would happen
ansible.builtin.debug:
msg: "Running in check mode - no changes will be made"
when: ansible_check_mode
- name: Only run during real execution
ansible.builtin.command: /opt/irreversible-action.sh
when: not ansible_check_modeDiff Mode: See Exact Changes
ansible-playbook site.yml --diff
ansible-playbook site.yml --check --diff# Enable diff for specific tasks
- name: Update config with diff
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
diff: trueOutput shows unified diff:
--- before: /etc/nginx/nginx.conf
+++ after: /tmp/nginx.conf
@@ -1,3 +1,3 @@
-worker_processes 2;
+worker_processes 4;See also: ACTION REQUIRED: Ansible Collections Must Add CI Test Runs Against Devel Branch
Check Mode with Registered Variables
- name: Check package status
ansible.builtin.apt:
name: nginx
state: present
check_mode: true
register: nginx_check
- name: Report if nginx needs installing
ansible.builtin.debug:
msg: "Nginx would be installed"
when: nginx_check.changedModules That Don't Support Check Mode
Some modules skip in check mode:
ansible.builtin.commandansible.builtin.shellansible.builtin.rawansible.builtin.script
- name: Run validation even in check mode
ansible.builtin.command: /opt/validate-config.sh
check_mode: false
register: validation
changed_when: falsePractical Patterns
Pre-deployment validation
# Step 1: Dry run
ansible-playbook deploy.yml --check --diff
# Step 2: Review output
# Step 3: Apply for real
ansible-playbook deploy.ymlCI/CD pipeline check
# .gitlab-ci.yml
ansible-check:
script:
- ansible-playbook site.yml --check --diff
only:
- merge_requests
ansible-apply:
script:
- ansible-playbook site.yml
only:
- mainFAQ
Is check mode 100% accurate?
No. Some tasks depend on previous task results that weren't actually applied. Commands modules can't predict output. Use it as a best-effort preview, not a guarantee.
Can I use check mode with ansible ad-hoc commands?
Yes: ansible all -m apt -a "name=nginx state=present" --check
What's the difference between --check and --syntax-check?
--syntax-check only validates YAML/playbook syntax. --check actually connects to hosts and simulates execution.
Run in Check Mode
# Dry run entire playbook
ansible-playbook site.yml --check
# Check mode with diff (show what would change)
ansible-playbook site.yml --check --diffPer-Task Check Mode
Always run (even in check mode)
- name: Gather info (always runs)
ansible.builtin.command: df -h
check_mode: false
changed_when: false
register: disk_infoNever run in check mode
- name: Dangerous operation
ansible.builtin.command: /opt/migrate.sh
check_mode: true # Only runs in check mode (skipped normally... wait, reversed)Conditional on check mode
- name: Only in real runs
ansible.builtin.service:
name: nginx
state: restarted
when: not ansible_check_mode
- name: Report what would happen
ansible.builtin.debug:
msg: "Would restart nginx"
when: ansible_check_modeDiff Mode
ansible-playbook site.yml --diffShows file content changes:
--- /etc/nginx/nginx.conf (before)
+++ /etc/nginx/nginx.conf (after)
@@ -1,3 +1,3 @@
-worker_processes 2;
+worker_processes 4;Combine check + diff
ansible-playbook site.yml --check --diffPreview ALL changes without applying them.
CI/CD Validation
# .github/workflows/ansible-lint.yml
- name: Syntax check
run: ansible-playbook site.yml --syntax-check
- name: Dry run
run: ansible-playbook site.yml --check --diff
env:
ANSIBLE_HOST_KEY_CHECKING: "false"Modules That Support Check Mode
Most built-in modules support check mode. They report what would change:
| Module | Check Mode Behavior |
|---|---|
copy | Reports if file would change |
template | Shows diff of rendered template |
package | Reports packages to install/remove |
service | Reports state changes |
file | Reports permission/ownership changes |
lineinfile | Reports line changes |
command | Always shows "skipping" (can't predict) |
Force Changed Status
- name: Command that check mode can't predict
ansible.builtin.command: /opt/deploy.sh
register: result
changed_when: "'deployed' in result.stdout"
check_mode: false # Always run, even in check modeFAQ
Why do some tasks show "skipping" in check mode?
Modules like command and shell can't predict their outcome. They skip in check mode unless you set check_mode: false.
Can handlers run in check mode?
No — handlers don't fire in check mode since no actual changes occur.
How do I test a playbook that depends on previous task results?
Use check_mode: false on gathering tasks so subsequent tasks have the data they need:
- name: Get current version
command: cat /opt/app/VERSION
register: version
check_mode: false
changed_when: falseRun in Check Mode
ansible-playbook site.yml --check
# Or short form
ansible-playbook site.yml -CCheck + Diff Mode
# Show what would change
ansible-playbook site.yml --check --diff
ansible-playbook site.yml -CDForce Task to Run in Check Mode
# This task always runs, even in check mode
- command: systemctl is-active nginx
register: nginx_status
check_mode: false # Always execute
changed_when: falseSkip Task in Check Mode
# This task is skipped in check mode
- command: /opt/scripts/deploy.sh
when: not ansible_check_modeDetect Check Mode in Tasks
- debug:
msg: "Running in {{ 'CHECK' if ansible_check_mode else 'NORMAL' }} mode"
- name: Deploy (skip in check mode)
command: /opt/deploy.sh
when: not ansible_check_mode
- name: Report what would happen
debug:
msg: "Would deploy version {{ app_version }}"
when: ansible_check_modePer-Task Check Mode
# Always check (never change)
- apt: name=nginx state=present
check_mode: true # Never actually installs
# Always run (even during --check)
- setup:
check_mode: false # Gather facts regardlessDiff Mode Output
ansible-playbook site.yml --diff# Shows file changes
TASK [Deploy config] ****
--- before: /etc/nginx/nginx.conf
+++ after: /tmp/nginx.conf
@@ -1,3 +1,3 @@
-worker_processes 4;
+worker_processes 8;
events {
worker_connections 1024;always_run (Deprecated)
# OLD (deprecated)
- command: uptime
always_run: true
# NEW
- command: uptime
check_mode: falsePractical Patterns
Validate Before Apply
# Step 1: Check what would change
ansible-playbook site.yml -CD
# Step 2: Review output
# Step 3: Apply changes
ansible-playbook site.ymlAudit Mode Playbook
---
- name: Security audit (check mode only)
hosts: all
tasks:
- name: Check for unpatched packages
apt:
upgrade: dist
check_mode: true
register: updates
- debug:
msg: "{{ updates.stdout_lines | select('match', '^Inst') | list | length }} updates available"
when: updates.changedModules That Support Check Mode
Most modules support check mode. Notable exceptions:
command/shell/raw— can't predict changesscript— can't predict outcome- Custom modules — depends on implementation
FAQ
Does check mode guarantee no changes?
No — modules that don't support check mode will be skipped (not simulated). Some modules may still make read-only API calls.
Can I enforce check mode for safety?
# ansible.cfg
[defaults]
# No setting to force check mode globally
# Use wrapper script or CI pipeline enforcementWhy does my playbook fail in check mode?
Tasks that register variables may return empty results in check mode, causing downstream tasks to fail. Use check_mode: false for prerequisite tasks or add when: result is not skipped.
Run in Check Mode
ansible-playbook site.yml --checkCheck + Diff
ansible-playbook site.yml --check --diff
# Shows what would change AND the actual diffPer-Task Check Mode
# Always run (even in check mode)
- command: cat /etc/os-release
check_mode: false
register: os_info
changed_when: false
# Always check (even in normal mode)
- apt:
name: nginx
state: present
check_mode: true # Never actually installs
register: would_change
- debug:
msg: "nginx needs installing"
when: would_change.changedHandle Tasks That Don't Support Check Mode
- command: /opt/deploy.sh
when: not ansible_check_mode # Skip in check mode
# Or use check_mode: false to force execution
- command: cat /opt/app/version
check_mode: false
register: current_version
changed_when: falseDiff Mode in Playbook
- template:
src: config.j2
dest: /etc/myapp/config.conf
diff: true # Always show diff for this taskPractical Patterns
# Validate before applying
# Step 1: Check mode
ansible-playbook site.yml --check --diff
# Step 2: Review output
# Step 3: Apply if satisfied
ansible-playbook site.ymlRegister in Check Mode
# Some modules report what WOULD change
- apt:
name: nginx
state: present
check_mode: true
register: apt_check
- debug:
msg: "{{ apt_check.changed | ternary('Would install', 'Already installed') }}"check_mode Variable
# ansible_check_mode is true during --check
- debug:
msg: "Running in check mode"
when: ansible_check_mode
- template:
src: dangerous-config.j2
dest: /etc/critical/config
when: not ansible_check_mode # Skip in dry runFAQ
Does --check make any changes?
No — most modules simulate the action and report what would change. Some modules (like command) just skip.
Can I check specific tasks only?
Use check_mode: true on individual tasks. Or use --check with --tags to limit scope.
What does "skipping" mean in check mode?
Some modules can't simulate (e.g., command, shell). They skip unless you set check_mode: false.
Related Articles
Category: installation