What Does when Do in Ansible?
The when keyword controls whether a task runs. It evaluates a Jinja2 expression — if true, the task executes; if false, it's skipped. Use it to target specific operating systems, check variable values, act on command output, or implement conditional logic.
Basic Syntax
tasks:
# Run only on Ubuntu
- name: Install apt packages
ansible.builtin.apt:
name: nginx
when: ansible_distribution == "Ubuntu"
# Run only on RedHat/CentOS
- name: Install yum packages
ansible.builtin.dnf:
name: nginx
when: ansible_os_family == "RedHat"
Test Variables
# Check if variable is defined
- name: Use custom port
ansible.builtin.debug:
msg: "Port is {{ custom_port }}"
when: custom_port is defined
# Check if undefined
- name: Use default port
ansible.builtin.debug:
msg: "Using default port 8080"
when: custom_port is not defined
# Check boolean
- name: Enable debug mode
ansible.builtin.template:
src: debug.conf.j2
dest: /etc/app/debug.conf
when: debug_mode | default(false)
# Check string value
- name: Production-only task
ansible.builtin.template:
src: prod.conf.j2
dest: /etc/app/config.yml
when: env == "production"
# Check if variable is truthy
- name: Run if enabled
ansible.builtin.debug:
msg: "Feature enabled"
when: feature_flag # True if non-empty, non-zero, non-false
Operating System Conditions
# By distribution
- ansible.builtin.apt:
name: nginx
when: ansible_distribution == "Ubuntu"
- ansible.builtin.dnf:
name: nginx
when: ansible_distribution == "CentOS"
# By OS family (covers multiple distros)
- ansible.builtin.apt:
name: nginx
when: ansible_os_family == "Debian" # Ubuntu, Debian, Mint
- ansible.builtin.dnf:
name: nginx
when: ansible_os_family == "RedHat" # RHEL, CentOS, Fedora, Rocky
# By version
- name: Ubuntu 22.04+ only
ansible.builtin.apt:
name: modern-package
when: ansible_distribution == "Ubuntu" and ansible_distribution_version is version('22.04', '>=')
# By architecture
- name: x86_64 only
ansible.builtin.debug:
msg: "64-bit system"
when: ansible_architecture == "x86_64"
Registered Results
# Check command return code
- name: Check if app is running
ansible.builtin.command: systemctl is-active myapp
register: app_status
ignore_errors: true
- name: Start app if not running
ansible.builtin.service:
name: myapp
state: started
when: app_status.rc != 0
# Check command output
- name: Get current version
ansible.builtin.command: /opt/app/bin/version
register: version_check
- name: Upgrade if outdated
ansible.builtin.shell: /opt/app/upgrade.sh
when: "'2.0' not in version_check.stdout"
# Check if file exists
- name: Check config exists
ansible.builtin.stat:
path: /etc/myapp/config.yml
register: config_file
- name: Create default config
ansible.builtin.template:
src: config.yml.j2
dest: /etc/myapp/config.yml
when: not config_file.stat.exists
Multiple Conditions
AND (all must be true)
# Method 1: and operator
- name: Production Ubuntu only
ansible.builtin.apt:
name: nginx
when: ansible_distribution == "Ubuntu" and env == "production"
# Method 2: list (implicit AND)
- name: Production Ubuntu only
ansible.builtin.apt:
name: nginx
when:
- ansible_distribution == "Ubuntu"
- env == "production"
- ansible_memtotal_mb > 2048
OR (any must be true)
- name: Install on Debian-family
ansible.builtin.apt:
name: nginx
when: ansible_distribution == "Ubuntu" or ansible_distribution == "Debian"
# Cleaner with 'in'
- name: Install on Debian-family
ansible.builtin.apt:
name: nginx
when: ansible_distribution in ["Ubuntu", "Debian", "Linux Mint"]
NOT
- name: Skip on production
ansible.builtin.debug:
msg: "Debug info"
when: env != "production"
# With 'not'
- name: Skip if flag is set
ansible.builtin.debug:
msg: "Feature disabled"
when: not feature_enabled
# Not in list
- name: Skip CentOS and Fedora
ansible.builtin.apt:
name: nginx
when: ansible_distribution not in ["CentOS", "Fedora"]
Complex Combinations
- name: Complex condition
ansible.builtin.template:
src: optimized.conf.j2
dest: /etc/app/config.yml
when: >
(env == "production" and ansible_memtotal_mb > 4096)
or
(env == "staging" and force_optimization | default(false))
Conditional with Loops
- name: Install packages only if needed
ansible.builtin.apt:
name: "{{ item.name }}"
loop:
- { name: nginx, install: true }
- { name: apache2, install: false }
- { name: redis, install: true }
when: item.install
- name: Create users on specific hosts
ansible.builtin.user:
name: "{{ item }}"
loop: "{{ admin_users }}"
when: inventory_hostname in groups['production']
Conditional with Blocks
# Apply condition to multiple tasks at once
- name: RedHat setup
when: ansible_os_family == "RedHat"
block:
- name: Install EPEL
ansible.builtin.dnf:
name: epel-release
- name: Install packages
ansible.builtin.dnf:
name:
- nginx
- redis
- name: Configure SELinux
ansible.posix.seboolean:
name: httpd_can_network_connect
state: true
Common Tests
# Type checks
when: my_var is string
when: my_var is number
when: my_var is mapping # dict
when: my_var is iterable # list or string
# Version comparison
when: ansible_distribution_version is version('22.04', '>=')
when: app_version is version('2.0', '<')
# String tests
when: "'error' in command_output.stdout"
when: hostname is match("web-.*")
when: hostname is search("prod")
# File tests (with stat)
when: file_stat.stat.exists
when: file_stat.stat.isdir
when: file_stat.stat.size > 0
# Truthiness
when: my_var # True if defined, non-empty, non-zero, non-false
when: my_var | length > 0 # Non-empty list/string
when: my_list | length # List has items
Troubleshooting
"Conditional check failed"
The variable is undefined. Use default():
# FAILS if env is not defined
when: env == "production"
# SAFE — defaults to empty string
when: env | default('') == "production"
Boolean Variable Issues
# WRONG — string "false" is truthy!
when: "{{ my_bool }}"
# RIGHT — bare variable reference
when: my_bool
# RIGHT — explicit comparison
when: my_bool | bool
when Doesn't Need {{ }}
# WRONG — don't use Jinja2 delimiters
when: "{{ ansible_distribution }} == 'Ubuntu'"
# RIGHT — when already evaluates as Jinja2
when: ansible_distribution == "Ubuntu"
Related Articles
- Ansible set_fact Module
- Ansible Facts Guide
- Ansible Handlers
- Ansible register Variables
- Ansible Blocks
Conclusion
when is how you make Ansible smart — run tasks only when they apply. Common patterns: OS-family branching (ansible_os_family), environment checks (env == "production"), and acting on command results (register + when: result.rc != 0). Use list format for multiple AND conditions, and block to apply one condition to many tasks. Remember: when already evaluates Jinja2 — don't wrap it in {{ }}.