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 --diff
See 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
Important: Not all modules support check mode. Modules that don't will be skipped with a warning.
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: false
See 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_mode
Diff 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: true
Output 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.changed
Modules That Don't Support Check Mode
Some modules skip in check mode:
• ansible.builtin.command
• ansible.builtin.shell
• ansible.builtin.raw
• ansible.builtin.script
Force them to run even in check mode:
- name: Run validation even in check mode
ansible.builtin.command: /opt/validate-config.sh
check_mode: false
register: validation
changed_when: false
Practical 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.yml
CI/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:
- main
FAQ
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 --diff
Per-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_info
Never 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_mode
Diff Mode
ansible-playbook site.yml --diff
Shows 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 --diff
Preview 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 mode
FAQ
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: false
Run in Check Mode
ansible-playbook site.yml --check
# Or short form
ansible-playbook site.yml -C
Check + Diff Mode
# Show what would change
ansible-playbook site.yml --check --diff
ansible-playbook site.yml -CD
Force 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: false
Skip Task in Check Mode
# This task is skipped in check mode
- command: /opt/scripts/deploy.sh
when: not ansible_check_mode
Detect 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_mode
Per-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 regardless
Diff 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: false
Practical 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.yml
Audit 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.changed
Modules That Support Check Mode
Most modules support check mode. Notable exceptions:
• command / shell / raw — can't predict changes
• script — 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 enforcement
Why 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 --check
Check + Diff
ansible-playbook site.yml --check --diff
# Shows what would change AND the actual diff
Per-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.changed
Handle 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: false
Diff Mode in Playbook
- template:
src: config.j2
dest: /etc/myapp/config.conf
diff: true # Always show diff for this task
Practical 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.yml
Register 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 run
FAQ
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
• Ansible template guide • task gating with Ansible when • Nginx vhost provisioning with Ansible • Ansible assert Module GuideCategory: installation