when: Running a Task Only Where It Applies
Scenario: A task guarded by when: inventory_hostname in dbservers has been skipping on every host for a year. Nobody noticed, because a skipped task is green.
New words, in plain English
when- a condition attached to a task. If it evaluates false, the task is skipped.- Bare expression -
whenis already inside Jinja2, so you writewhen: app_env == 'production'- no braces. group_names- the list of groups the current host belongs to.- Truthy - values Ansible treats as true:
true,yes,1, a non-empty string.
when is the mechanism for "this task applies to some hosts and not others". It is evaluated per host, so one play can converge a heterogeneous estate.
The single most important syntax rule: when is already a Jinja2 expression context, so you do not wrap it in {{ }}. Write when: app_env == 'production', not when: "{{ app_env == 'production' }}". The braced form usually still works, but it produces a string that Ansible then re-evaluates, and the failure modes are confusing.
The scenario above shows the dangerous pattern. dbservers is a group name, not a variable. In a Jinja2 expression it is an undefined name, so the condition is false everywhere - forever, silently, greenly. The correct forms are:
when: "'dbservers' in group_names"- is this host in that group?when: inventory_hostname in groups['dbservers']- is this host in that group's member list?
Conditions combine with and, or, not, and a list of conditions is implicitly ANDed, which reads more clearly than a long single line.
Analogy: when is the bouncer's list. A name that is not on the list is turned away quietly - no announcement, no complaint. If you wrote the list wrong, everyone gets turned away and the venue simply stays empty all night.
A worked example
# WRONG - `dbservers` is a group name, not a variable. Always false.
- name: Install DB tooling
ansible.builtin.package:
name: postgresql-client
when: inventory_hostname in dbservers
# RIGHT - two equivalent, correct forms
when: "'dbservers' in group_names"
when: inventory_hostname in groups['dbservers']
# A list of conditions is ANDed - and reads far better
- name: Restart only healthy production web servers
ansible.builtin.service:
name: app
state: restarted
when:
- app_env == 'production'
- "'webservers' in group_names"
- health.rc == 0
# Branching on a fact
when: ansible_facts['os_family'] == 'Debian'
Because a skipped task is reported green, a broken condition is invisible in normal output. Two habits catch it. Run with -v and read the skipping: lines - if a task skips on every single host, that is a red flag, not a success. And in ansible.cfg, display_skipped_hosts = False hides skips to reduce noise, which is fine for a converged estate and actively harmful while you are still developing a play.
When a task depends on a registered result, remember that the result exists only if the earlier task ran. A when on the first task that skips it leaves the second task referencing an object with no stdout, which fails with a message about a dict having no attribute.
Warning: A when that is false on every host produces a completely green run in which nothing happened. Green is not the same as correct.
Goal: Put this to work in the ansible-loops-conditionals lab. Open/labs/ansible, pickansible-loops-conditionals, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.