Conditionals Usage in Ansible Playbook
Last updated: June 29, 2025
My Journey with Conditionals in Ansible
When I first started with Ansible, my playbooks were pretty straightforward—install a package, copy some files, restart a service. But as my infrastructure grew more complex, I realized that I needed my automation to be smarter and more adaptable. That's when I discovered the power of conditionals in Ansible playbooks.
Learning to use conditionals effectively was a game-changer in my automation journey. Instead of writing separate playbooks for different environments or scenarios, I could create a single, intelligent playbook that could make decisions based on facts, variables, and previous task results. This approach dramatically reduced duplication in my code and made my automation much more maintainable.
In this blog post, I'll share my experience with using conditionals in Ansible playbooks, with practical examples for both Linux and Windows environments. Whether you're managing a handful of servers or a complex multi-platform infrastructure, mastering conditionals will take your Ansible automation to the next level.
Understanding Conditionals in Ansible
Conditionals in Ansible let you control the execution flow of your playbooks by making tasks run only when certain conditions are met. This is similar to if/elif/else
statements in traditional programming languages, but with Ansible's declarative approach.
The most common way to implement conditionals in Ansible is through the when
statement, which uses Jinja2 expressions to evaluate conditions. Unlike other places where you'd use Jinja2 syntax in Ansible, you typically don't need to surround expressions with {{ }}
in a when
statement.
Basic Conditionals with When
The when
statement is the foundation of conditional execution in Ansible. Here's a simple example from my early days of using conditionals:
---
- name: Install appropriate web server based on OS
hosts: all
tasks:
- name: Install Apache on Debian/Ubuntu systems
apt:
name: apache2
state: present
when: ansible_facts['os_family'] == "Debian"
- name: Install Apache on RHEL/CentOS systems
yum:
name: httpd
state: present
when: ansible_facts['os_family'] == "RedHat"
- name: Install IIS on Windows systems
win_feature:
name: Web-Server
state: present
when: ansible_facts['os_family'] == "Windows"
In this example, the appropriate web server installation task runs only on systems that match the specified OS family. Tasks with conditions that evaluate to false
are skipped completely.
Comparison Operators
Conditions in Ansible use standard comparison operators that you might recognize from other programming languages:
==
- Equal to!=
- Not equal to>
- Greater than<
- Less than>=
- Greater than or equal to<=
- Less than or equal to
Here's an example checking disk space before performing a large file operation:
- name: Check available disk space
shell: df -h / | grep -v Filesystem | awk '{print $5}' | sed 's/%//'
register: disk_space
changed_when: false # This is just a check, no change should be recorded
- name: Perform large file operation
ansible.builtin.copy:
src: /path/to/large/file
dest: /destination/path
when: disk_space.stdout|int < 80 # Only run if disk is less than 80% full
Conditionals Based on Ansible Facts
One of the most powerful uses of conditionals is with Ansible facts. Facts are pieces of information about your managed hosts that Ansible gathers automatically when running playbooks.
Here's an example from a cross-platform automation project I worked on:
---
- name: Configure appropriate firewall based on OS
hosts: all
tasks:
- name: Configure UFW on Ubuntu
block:
- name: Ensure UFW is installed
apt:
name: ufw
state: present
- name: Allow HTTP traffic
ufw:
rule: allow
port: 80
proto: tcp
when: ansible_facts['distribution'] == "Ubuntu"
- name: Configure firewalld on CentOS/RHEL 7+
block:
- name: Ensure firewalld is installed
yum:
name: firewalld
state: present
- name: Allow HTTP traffic
firewalld:
service: http
permanent: yes
state: enabled
when:
- ansible_facts['distribution'] == "CentOS" or ansible_facts['distribution'] == "RedHat"
- ansible_facts['distribution_major_version'] | int >= 7
- name: Configure Windows Firewall
win_firewall_rule:
name: Allow HTTP
localport: 80
action: allow
direction: in
protocol: tcp
state: present
enabled: yes
when: ansible_facts['os_family'] == "Windows"
Multiple Conditions
You can use logical operators to combine multiple conditions:
and
- Both conditions must be trueor
- Either condition can be truenot
- Inverts the condition
For complex conditions, you can use parentheses to group expressions or use a list format for improved readability:
# Using parentheses for grouping
- name: Task with complex condition
debug:
msg: "This will run on CentOS 7+ or any Ubuntu"
when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] | int >= 7) or ansible_facts['distribution'] == "Ubuntu"
# Using list format (all items are ANDed together)
- name: Task with list conditions
debug:
msg: "This will run only on CentOS 8"
when:
- ansible_facts['distribution'] == "CentOS"
- ansible_facts['distribution_major_version'] | int == 8
Conditionals Based on Registered Variables
Another powerful way to use conditionals is with the register
directive, which lets you capture the output from one task and use it to make decisions in subsequent tasks.
Here's a real-world example I use to check if a service is running before attempting to restart it:
---
- name: Manage application service
hosts: app_servers
tasks:
- name: Check if service exists
command: systemctl status my_application
register: service_status
ignore_errors: yes
changed_when: false
- name: Install service if it doesn't exist
include_tasks: install_service.yml
when: service_status.rc != 0
- name: Restart service if it exists
systemd:
name: my_application
state: restarted
when: service_status.rc == 0
In Windows environments, I often use PowerShell to gather information that influences subsequent tasks:
---
- name: Manage Windows features
hosts: windows_servers
tasks:
- name: Check if IIS is installed
win_shell: Get-WindowsFeature -Name Web-Server | Select-Object -ExpandProperty InstallState
register: iis_status
changed_when: false
- name: Display installation status
debug:
msg: "IIS is already installed"
when: iis_status.stdout | trim == "Installed"
- name: Install IIS if not present
win_feature:
name: Web-Server
state: present
include_management_tools: yes
when: iis_status.stdout | trim != "Installed"
Testing for Success, Failure, or Change
Ansible provides special tests for registered variables:
when: result is success
- Task ran successfully (return code 0)when: result is failed
- Task failed (return code not 0)when: result is changed
- Task made changeswhen: result is skipped
- Task was skipped
Here's how I use these in my playbooks:
- name: Try to create a database
mysql_db:
name: my_app_db
state: present
register: db_creation
ignore_errors: yes
- name: Handle successful creation
debug:
msg: "Database was successfully created"
when: db_creation is success
- name: Handle failed creation - maybe create user first
include_tasks: create_db_user.yml
when: db_creation is failed
Conditionals Based on Variables
You can also use variables defined in your inventory, playbooks, or vars files as the basis for conditionals.
Boolean Variables
Boolean variables make for clean, readable conditionals:
---
- name: Configure environment based on variables
hosts: all
vars:
enable_monitoring: true
enable_backups: false
production_environment: true
tasks:
- name: Install monitoring agent
include_tasks: install_monitoring.yml
when: enable_monitoring
- name: Configure backup agent
include_tasks: setup_backup.yml
when: enable_backups
- name: Apply production hardening
include_tasks: harden_system.yml
when: production_environment
Testing for Variable Definition
Sometimes you need to check if a variable is defined before using it:
- name: Install custom package if specified
package:
name: "{{ custom_package }}"
state: present
when: custom_package is defined
- name: Use default package if custom not specified
package:
name: default-package
state: present
when: custom_package is not defined
This pattern is particularly useful when you have optional configurations or when you're building playbooks that can be customized via extra vars.
Advanced Conditional Techniques
Over time, I've developed some more sophisticated conditional techniques that have proven invaluable in complex automation scenarios.
Using Blocks with Conditionals
The block
directive lets you apply a condition to multiple tasks at once, which can make your playbooks more readable and maintainable:
- name: Database server configuration
hosts: db_servers
tasks:
- block:
- name: Install MySQL
package:
name: mysql-server
state: present
- name: Start MySQL service
service:
name: mysqld
state: started
enabled: yes
- name: Configure MySQL settings
template:
src: mysql.cnf.j2
dest: /etc/my.cnf
when: db_type == "mysql"
- block:
- name: Install PostgreSQL
package:
name: postgresql-server
state: present
- name: Initialize PostgreSQL database
command: postgresql-setup initdb
args:
creates: /var/lib/pgsql/data/pg_hba.conf
- name: Start PostgreSQL service
service:
name: postgresql
state: started
enabled: yes
when: db_type == "postgresql"
Error Handling with Rescue and When
You can combine conditionals with Ansible's error handling features for more robust playbooks:
- name: Attempt critical operation with fallback
block:
- name: Try primary method
shell: /usr/local/bin/critical_operation.sh
register: primary_result
rescue:
- name: Try secondary method if primary fails
shell: /usr/local/bin/fallback_operation.sh
when: primary_result is defined and primary_result is failed
when: perform_critical_operation | bool
This pattern has saved me many times in production environments where operations might fail and need alternative approaches.
Sequence Diagram: Conditional Execution Flow
To visualize how conditionals affect the execution flow in Ansible playbooks, I've created this sequence diagram:
This diagram illustrates how Ansible evaluates conditions before executing each task, and how the results of previous tasks can influence subsequent conditional evaluations.
Best Practices for Using Conditionals
Through my experience with Ansible, I've developed these best practices for working with conditionals:
1. Keep Conditions Simple and Readable
Break complex conditions into smaller, more manageable pieces:
# Instead of this:
when: (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] | int >= 7) or (ansible_facts['distribution'] == "Ubuntu" and ansible_facts['distribution_major_version'] | int >= 18)
# Do this:
when:
- ansible_facts['distribution'] in ["CentOS", "Ubuntu"]
- (ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] | int >= 7) or
(ansible_facts['distribution'] == "Ubuntu" and ansible_facts['distribution_major_version'] | int >= 18)
2. Use Blocks for Related Tasks
Group related tasks under a single condition using blocks:
- block:
- name: Task 1
# task details...
- name: Task 2
# task details...
- name: Task 3
# task details...
when: some_condition
3. Consider Variable Types
Be mindful of variable types, especially when comparing numbers:
# This might not work as expected if version is a string
when: version > 7
# Better approach
when: version | int > 7
4. Test Conditions Before Deployment
Use ansible-playbook --check
with liberal debug statements to verify your conditions are evaluating as expected:
- name: Debug condition evaluation
debug:
msg: "Condition evaluated to True"
when: complex_condition
tags: always
Real-World Examples
Here are some examples from my actual automation projects that demonstrate the power of conditionals in Ansible:
Cross-Platform Package Installation
---
- name: Install required packages across platforms
hosts: all
tasks:
- name: Install packages on Debian/Ubuntu
apt:
name: "{{ debian_packages }}"
state: present
update_cache: yes
when: ansible_facts['os_family'] == "Debian"
vars:
debian_packages:
- curl
- vim
- htop
- iotop
- name: Install packages on RHEL/CentOS
yum:
name: "{{ rhel_packages }}"
state: present
when: ansible_facts['os_family'] == "RedHat"
vars:
rhel_packages:
- curl
- vim-enhanced
- htop
- iotop
- name: Install packages on Windows
win_chocolatey:
name: "{{ windows_packages }}"
state: present
when: ansible_facts['os_family'] == "Windows"
vars:
windows_packages:
- curl
- vim
- procexp
Environment-Specific Configuration
---
- name: Deploy application with environment-specific config
hosts: app_servers
vars:
environments:
development:
db_host: dev-db.example.com
log_level: debug
feature_flags:
new_ui: true
analytics: false
production:
db_host: prod-db.example.com
log_level: info
feature_flags:
new_ui: false
analytics: true
tasks:
- name: Set environment-specific variables
set_fact:
db_host: "{{ environments[deploy_environment].db_host }}"
log_level: "{{ environments[deploy_environment].log_level }}"
feature_flags: "{{ environments[deploy_environment].feature_flags }}"
when: deploy_environment in environments
- name: Deploy application configuration
template:
src: app-config.json.j2
dest: /opt/myapp/config.json
when: deploy_environment in environments
- name: Enable feature flags based on environment
debug:
msg: "New UI is {{ 'enabled' if feature_flags.new_ui else 'disabled' }}"
when: feature_flags is defined and feature_flags.new_ui is defined
Self-Healing Infrastructure
One of my favorite uses of conditionals is creating self-healing infrastructure:
---
- name: Self-healing application deployment
hosts: app_servers
tasks:
- name: Check application health
uri:
url: http://localhost:8080/health
status_code: 200
register: health_check
ignore_errors: yes
- name: Backup database before recovery
mysql_db:
name: myapp
state: dump
target: /backup/myapp-{{ ansible_date_time.iso8601 }}.sql
when: health_check is failed
- name: Attempt application restart
systemd:
name: myapp
state: restarted
when: health_check is failed
register: restart_attempt
- name: Restore from latest backup if restart fails
block:
- name: Find latest backup
find:
paths: /backup
patterns: 'myapp-*.sql'
sort: 'mtime'
register: backups
- name: Restore database from backup
mysql_db:
name: myapp
state: import
target: "{{ backups.files[-1].path }}"
when: backups.matched > 0
when: restart_attempt is failed or restart_attempt is skipped
Conclusion
Mastering conditionals has transformed how I approach Ansible automation. Instead of creating separate playbooks for different scenarios, I now build intelligent, adaptive playbooks that respond to the actual state of my infrastructure.
Conditionals make Ansible playbooks more powerful, flexible, and maintainable. They allow you to handle edge cases gracefully, adapt to different environments, and implement sophisticated automation logic without leaving the comfort of Ansible's declarative approach.
As you build your own automation, I encourage you to experiment with conditionals. Start simple, and as you gain confidence, you'll find yourself handling increasingly complex scenarios with elegant, readable code. The path to truly powerful automation is through smart, conditional execution.
Last updated