Have you ever had an Ansible task report “changed” even though it didn’t really change anything? Or worse, mark a task as “failed” when the error was harmless?
That’s where changed_when and failed_when come into play. These directives give you full control over how Ansible interprets task results—so your automation is accurate, clean, and smart.
This guide explores how to use changed_when and failed_when with practical examples.
Table of Contents
What Are changed_when
and failed_when
?
Directive | Purpose |
---|---|
changed_when | Controls whether a task is marked as changed, regardless of module behavior. |
failed_when | Controls whether a task is marked as failed, even if no error occurred. |
By default:
A task is considered “changed” if it performs any action.
A task fails if it returns a non-zero exit code or an error.
These directives override that behavior using conditions based on registered variables.
Example 1: Avoid False Positives When Creating Files
You want to create a file only if it doesn’t exist and prevent Ansible from falsely reporting it as “changed” when it’s already there.
- name: Check and create file if not exists
hosts: localhost
tasks:
- name: Check if file exists
stat:
path: /tmp/example_file.txt
register: file_stat
- name: Create file if it does not exist
file:
path: /tmp/example_file.txt
state: touch
changed_when: not file_stat.stat.exists
In this example:
- stat module checks if the file /tmp/example_file.txt exists, storing the result in file_stat.
- The file module creates the file only if it doesn’t exist. The changed_when directive uses the condition not file_stat.stat.exists to ensure that Ansible reports a change only if the file was created.
Example 2: Fail When Disk Usage Hits Threshold
You want to fail a task if disk usage on /dev/sda1
exceeds 80%, but not always treat it as a failure.
This playbook checks the disk usage of /var and fails if /dev/sda1 has reached 80% capacity.
- name: Check disk usage
hosts: localhost
tasks:
- name: Get disk usage of /var
command: df -h /var
register: disk_usage
changed_when: false
failed_when: "'/dev/sda1' in disk_usage.stdout and '80%' in disk_usage.stdout"
In this example:
- Prevents the task from reporting as “changed”.
- Triggers failure only when /dev/sda1 hits 80% usage.
Example 3: Start a Service If Inactive, Fail If Restart Fails
Imagine a task that checks if a service is running and starts it if it is not. Additionally, we want to fail the task if the service fails to start.
This playbook checks if my_service is running and attempts to start it if it is not, marking the task as changed and failing the task if starting the service fails.
- name: Ensure my_service is running
hosts: localhost
tasks:
- name: Check if my_service is running
shell: systemctl is-active my_service
register: service_status
ignore_errors: yes
- name: Start my_service if not running
shell: systemctl start my_service
when: service_status.stdout != 'active'
changed_when: service_status.stdout != 'active'
failed_when: service_status.stdout != 'active' and service_status.rc != 0
In this example:
- Service starts only if it was not active.
- Reports as “changed” only when needed.
- Fails if systemctl returns an error during the start process.
Example 4: Fail if No Process is Listening on Port 80
You want to ensure a service is listening on port 80, and fail explicitly if not.
This playbook checks if a process is running on port 80 and fails with a specific message if no process is found.
- name: Check if process is running on port 80
hosts: localhost
tasks:
- name: Get process ID using port 80
shell: netstat -tuln | grep ':80'
register: process_check
ignore_errors: yes
- name: Fail if no process is found on port 80
fail:
msg: "No process found running on port 80."
failed_when: process_check.stdout == ''
In this example:
- netstat command checks for any process listening on port 80, storing the output in process_check.
- ignore_errors: yes allows the playbook to continue even if the command fails (e.g., if the port is not in use).
- failed_when directive triggers a failure if process_check.stdout is empty, indicating no process was found on port 80.
Example 5: Report Change Only on Apache Restart
Imagine we have a playbook that checks the status of a web server and wants to report a change only if the server was not previously running and is started by the playbook.
This playbook checks the status of the Apache web server and starts it if it is not active, marking the task as changed if the server was either inactive or failed.
- name: Check and start web server
hosts: localhost
tasks:
- name: Check web server status
command: systemctl is-active apache2
register: webserver_status
ignore_errors: yes
- name: Start web server
command: systemctl start apache2
when: webserver_status.stdout != 'active'
changed_when: "'inactive' in webserver_status.stdout or 'failed' in webserver_status.stdout"
In this example:
- systemctl is-active apache2 command checks if the Apache web server is running, with the result stored in webserver_status.
- systemctl start apache2 command starts the web server only if it is not active.
- changed_when directive reports a change if the web server was either ‘inactive’ or ‘failed’ before the playbook ran.
Example 6: Use changed_when for Idempotent Config Changes
You only want to change a config if a specific setting is missing—and not every time the playbook runs.
This playbook checks the current configuration in /etc/myapp/config and sets the correct configuration if the setting correct_setting=true is not found, marking the task as changed if the configuration needs to be updated.
- name: Ensure correct configuration is set
hosts: localhost
tasks:
- name: Check current configuration
command: cat /etc/myapp/config
register: current_config
- name: Set correct configuration
command: echo "correct_setting=true" > /etc/myapp/config
when: "current_config.stdout.find('correct_setting=true') == -1"
changed_when: "current_config.stdout.find('correct_setting=true') == -1"
In this example:
- cat command checks the current configuration, with the output stored in current_config.
- command echo “correct_setting=true” > /etc/myapp/config sets the configuration only if the correct setting is not already present.
- changed_when directive reports a change only if the setting was not already present.
Troubleshooting Tips
Problem | Fix |
---|---|
Task always marked as changed | Add changed_when: false or use conditionally |
Task fails with harmless errors | Use failed_when: <condition> or ignore_errors |
Want both conditions together | You can combine changed_when + failed_when |
Need debug info | Use debug: to print register outputs |
Conclusion
Ansible’s changed_when and failed_when give you powerful control over task outcomes, helping you:
- Avoid false change reports
- Prevent unnecessary failures
- Build idempotent, intelligent automation workflows
Use them wisely to improve playbook clarity, accuracy, and error handling.
FAQs
1. Can I use changed_when with registered variables?
Yes, changed_when can be used with registered variables to conditionally mark a task as changed based on the output of previous tasks.
2. How can failed_when help in handling errors?
failed_when allows you to define specific failure conditions, preventing unnecessary task failures due to non-critical errors or warnings.
3. Can changed_when and failed_when be used together in the same task?
Yes, both changed_when and failed_when can be used in a single task to control how a task's result is interpreted and whether it should be marked as changed or failed.