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"

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 {{ }}.