
How to Use Conditionals in Ansible Playbooks
Ansible conditionals are a powerful feature that allow you to control the execution flow of your playbooks based on specific conditions. These conditionals help you build flexible, reusable automation scripts that can adapt to different environments, operating systems, and server configurations. By the end of this guide, you’ll understand how to implement various conditional statements, troubleshoot common issues, and optimize your playbooks for better performance.
How Ansible Conditionals Work
Ansible conditionals use the when
statement to evaluate expressions and determine whether a task should run. The conditional logic leverages Jinja2 templating engine, which allows you to use variables, facts, and complex expressions. The when
clause evaluates to either True or False, and tasks only execute when the condition is True.
The basic syntax follows this pattern:
- name: Example task with conditional
command: echo "This runs conditionally"
when: some_variable == "expected_value"
Ansible automatically gathers facts about target hosts, which you can use in conditionals. These facts include information about the operating system, hardware, network configuration, and more. You can also define custom variables and use them in your conditional logic.
Step-by-Step Implementation Guide
Let’s start with basic conditional implementations and gradually move to more complex scenarios.
Basic Variable-Based Conditionals
---
- name: Basic conditional playbook
hosts: all
vars:
environment_type: "production"
install_debug_tools: false
tasks:
- name: Install production packages
package:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql
when: environment_type == "production"
- name: Install debug tools
package:
name: "{{ item }}"
state: present
loop:
- htop
- strace
- tcpdump
when: install_debug_tools | bool
Operating System-Based Conditionals
- name: OS-specific tasks
hosts: all
tasks:
- name: Install Apache on Ubuntu/Debian
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: Install Apache on CentOS/RHEL
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
- name: Configure firewall on Ubuntu
ufw:
rule: allow
port: '80'
when: ansible_distribution == "Ubuntu"
Complex Conditional Logic
- name: Complex conditionals
hosts: all
tasks:
- name: Install Docker on suitable systems
package:
name: docker.io
state: present
when:
- ansible_architecture == "x86_64"
- ansible_memtotal_mb >= 2048
- ansible_os_family in ["Debian", "RedHat"]
- name: Configure high-memory settings
lineinfile:
path: /etc/mysql/mysql.conf.d/mysqld.cnf
line: "innodb_buffer_pool_size = 2G"
when: ansible_memtotal_mb > 4096 and mysql_installed is defined
Real-World Examples and Use Cases
Environment-Specific Deployment
Here’s a practical example for deploying applications across different environments:
---
- name: Environment-specific deployment
hosts: webservers
vars:
app_environment: "{{ environment | default('development') }}"
tasks:
- name: Set development database connection
template:
src: config.dev.j2
dest: /app/config.yaml
when: app_environment == "development"
- name: Set production database connection
template:
src: config.prod.j2
dest: /app/config.yaml
when: app_environment == "production"
- name: Enable SSL in production
lineinfile:
path: /etc/nginx/sites-available/default
regexp: '^#.*ssl_certificate'
line: ' ssl_certificate /etc/ssl/certs/app.crt;'
when: app_environment == "production"
notify: restart nginx
- name: Install monitoring tools in production
package:
name: "{{ item }}"
state: present
loop:
- prometheus-node-exporter
- collectd
when:
- app_environment == "production"
- monitoring_enabled | default(true) | bool
Hardware-Specific Configuration
- name: Hardware-optimized configurations
hosts: database_servers
tasks:
- name: Configure for high-memory systems
blockinfile:
path: /etc/postgresql/postgresql.conf
block: |
shared_buffers = 4GB
effective_cache_size = 12GB
maintenance_work_mem = 1GB
when: ansible_memtotal_mb >= 16384
- name: Configure for standard systems
blockinfile:
path: /etc/postgresql/postgresql.conf
block: |
shared_buffers = 512MB
effective_cache_size = 2GB
maintenance_work_mem = 256MB
when: ansible_memtotal_mb < 16384
- name: Enable SSD optimizations
lineinfile:
path: /etc/postgresql/postgresql.conf
line: "random_page_cost = 1.1"
when: ansible_devices.sda.rotational == "0"
Conditional Types and Operators
Ansible supports various conditional operators and patterns. Here's a comprehensive breakdown:
Operator Type | Syntax | Example | Use Case |
---|---|---|---|
Equality | ==, != | when: os_family == "RedHat" | Exact matches |
Comparison | >, <, >=, <= | when: ansible_memtotal_mb > 4096 | Numeric comparisons |
Membership | in, not in | when: item in valid_users | List/string containment |
Boolean | and, or, not | when: prod and ssl_enabled | Logic combinations |
Pattern | match, search | when: hostname | match("web.*") | Regex patterns |
Existence | is defined, is undefined | when: custom_var is defined | Variable existence |
Advanced Conditional Patterns
Conditional Imports and Includes
- name: Conditional task inclusion
hosts: all
tasks:
- name: Include OS-specific tasks
include_tasks: "{{ ansible_os_family | lower }}.yml"
when: ansible_os_family in ["RedHat", "Debian"]
- name: Import database tasks for DB servers
import_tasks: database.yml
when: "'database' in group_names"
- name: Include security hardening
include_tasks: security.yml
when:
- security_level is defined
- security_level in ["high", "maximum"]
Conditional Blocks
- name: Conditional blocks example
hosts: webservers
tasks:
- name: Production-only configuration block
block:
- name: Install production certificates
copy:
src: "{{ item }}"
dest: /etc/ssl/certs/
loop:
- production.crt
- production.key
- name: Configure production logging
template:
src: prod-logging.conf.j2
dest: /etc/rsyslog.d/app.conf
- name: Set production file permissions
file:
path: /var/log/app
mode: '0750'
owner: app
group: adm
rescue:
- name: Fallback configuration
debug:
msg: "Production setup failed, using defaults"
when: environment == "production"
Performance Considerations and Best Practices
Understanding the performance impact of conditionals helps you write efficient playbooks:
Pattern | Performance Impact | Best Practice | Alternative |
---|---|---|---|
Simple variable checks | Minimal (<1ms) | Use liberally | N/A |
Complex expressions | Low (1-5ms) | Cache results in variables | set_fact for reuse |
External command checks | High (100-1000ms) | Avoid in loops | Register once, reuse |
File existence checks | Medium (10-50ms) | Use stat module | Register stat results |
Optimized Conditional Patterns
- name: Efficient conditional usage
hosts: all
tasks:
# Good: Cache expensive operations
- name: Check if application is installed
stat:
path: /usr/bin/myapp
register: app_installed
- name: Configure application
template:
src: app.conf.j2
dest: /etc/myapp/config.conf
when: app_installed.stat.exists
# Good: Use set_fact for complex logic
- name: Determine server role
set_fact:
is_primary: "{{
inventory_hostname == groups['webservers'][0] and
environment == 'production' and
ansible_memtotal_mb > 8192
}}"
- name: Configure primary server
include_tasks: primary_setup.yml
when: is_primary | bool
# Bad: Expensive operation in loop
# - name: Check each file (inefficient)
# command: test -f /path/{{ item }}
# loop: "{{ many_files }}"
# when: check_files | bool
Common Pitfalls and Troubleshooting
String vs Boolean Comparisons
One of the most common issues involves mixing string and boolean values:
# Problem: String comparison
- name: This might not work as expected
debug:
msg: "SSL is enabled"
when: ssl_enabled == "true" # String comparison
# Solution: Boolean conversion
- name: This works correctly
debug:
msg: "SSL is enabled"
when: ssl_enabled | bool # Explicit boolean conversion
# Alternative: Direct boolean check
- name: Even better approach
debug:
msg: "SSL is enabled"
when: ssl_enabled # Direct boolean evaluation
Undefined Variable Handling
# Problem: Undefined variable causes failure
- name: Risky conditional
debug:
msg: "Custom configuration"
when: custom_config == "enabled"
# Solution: Check if defined first
- name: Safe conditional
debug:
msg: "Custom configuration"
when:
- custom_config is defined
- custom_config == "enabled"
# Alternative: Use default filter
- name: Default value approach
debug:
msg: "Custom configuration"
when: custom_config | default('disabled') == "enabled"
List and Dictionary Conditionals
- name: Working with complex data structures
hosts: all
vars:
allowed_users:
- admin
- developer
- support
service_config:
nginx:
port: 80
ssl: true
apache:
port: 8080
ssl: false
tasks:
- name: Check user access
debug:
msg: "Access granted"
when: ansible_user in allowed_users
- name: Configure SSL services
debug:
msg: "Configuring SSL for {{ item.key }}"
loop: "{{ service_config | dict2items }}"
when: item.value.ssl | bool
Integration with Configuration Management
When deploying on managed infrastructure like VPS or Dedicated Servers, conditionals become crucial for handling different server specifications and configurations:
- name: Server-tier specific configuration
hosts: all
tasks:
- name: Detect server tier based on resources
set_fact:
server_tier: "{%- if ansible_memtotal_mb >= 32768 -%}
enterprise
{%- elif ansible_memtotal_mb >= 8192 -%}
professional
{%- else -%}
standard
{%- endif -%}"
- name: Configure for enterprise tier
template:
src: enterprise.conf.j2
dest: /etc/app/config.conf
when: server_tier == "enterprise"
- name: Set resource limits based on tier
lineinfile:
path: /etc/systemd/system/myapp.service
regexp: '^LimitNOFILE='
line: "LimitNOFILE={{ 65536 if server_tier == 'enterprise' else 32768 }}"
Testing and Debugging Conditionals
Use these techniques to debug conditional logic:
- name: Debug conditional logic
hosts: all
tasks:
- name: Show all relevant facts
debug:
msg: |
OS Family: {{ ansible_os_family }}
Distribution: {{ ansible_distribution }}
Memory: {{ ansible_memtotal_mb }}MB
Architecture: {{ ansible_architecture }}
- name: Test complex conditional
debug:
msg: "Conditional result: {{
(ansible_os_family == 'RedHat') and
(ansible_memtotal_mb > 4096) and
(environment | default('dev') == 'prod')
}}"
- name: Use assert for validation
assert:
that:
- ansible_os_family in ["RedHat", "Debian"]
- ansible_memtotal_mb >= 1024
fail_msg: "Server doesn't meet minimum requirements"
success_msg: "Server configuration validated"
For comprehensive information about Ansible conditionals, refer to the official Ansible documentation. The Jinja2 templating documentation also provides valuable insights into the underlying templating engine that powers Ansible's conditional logic.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.