Ansible, one of the most popular open-source automation tools, helps IT professionals and DevOps teams automate various infrastructure tasks. When creating Ansible playbooks, you often need to execute tasks multiple times or conditionally. This is where loops and conditional tasks come into play.
In this article, we will delve into loops and conditional tasks in Ansible, exploring how they function, how to implement them in your playbooks, and how they can streamline automation processes.
Table of Contents
What Are Loops in Ansible?
Loops in Ansible allow you to repeat tasks multiple times, making automation simpler and reducing redundancy in your playbooks. Rather than writing the same task multiple times for different items (e.g., multiple users, packages, or files), you can use loops to handle them all in one go. This enhances code readability and maintainability.
When using Ansible loops you also reduce the possibility of human errors, as consistency is guaranteed regardless of the complexity of the task.
Common Loop Types in Ansible
Ansible supports several ways to implement loops. The most common ones include:
- Simple Loops: Iterate over a list of items.
- Loop with Dictionary: Iterate over key-value pairs in a dictionary.
- Loop with Indexes: Useful for scenarios where you need the index of the item in the loop.
- Loop with JSON Data: Loops can be used with structured data like JSON or YAML to process lists of objects.
Simple Loops
A simple loop iterates a task over a list of items. The loop keyword is added to the task, and takes as a value the list of items over which the task should be iterated. The loop variable item holds the value used during each iteration.
Consider the following snippet that uses the service module twice in order to ensure two network services are running:
- name: Postfix is running
service:
name: postfix
state: started
- name: Dovecot is running
service:
name: dovecot
state: started
These two tasks can be rewritten to use a simple loop so that only one task is needed to ensure both services are running:
- name: Postfix and Dovecot are running
service:
name: "{{ item }}"
state: started
loop:
- postfix
- dovecot
The list used by loop can be provided by a variable. In the following example, the variableb mail_services contains the list of services that need to be running.
vars:
mail_services:
- postfix
- dovecot
tasks:
- name: Postfix and Dovecot are running
service:
name: "{{ item }}"
state: started
loop: "{{ mail_services }}"
Loops over a List of Hashes or Dictionaries
Using Dictionaries in the Ansible looping feature can be very useful and make your playbooks more efficient and easier to manage. Dictionaries allow you to iterate over key-value pairs and perform operations on multiple items at once with its own set of values. This provides more scalability and flexibility throughout the playbook as you can add more items to your dictionary without changing the logic of your tasks.
Example 1:
- name: Users exist and are in the correct groups
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- name: jane
groups: wheel
- name: joe
groups: root
The outcome of the preceding task is that the user jane is present and a member of the group wheel, and that the user joe is present and a member of the group root.
What is the difference between with_items and loop in Ansible?
Historically, Ansible with_items was used to perform looping functions throughout playbooks. Due to its limitations, loops were introduced in Ansible 2.5. Even though Ansible’s with_items is still functional and not yet deprecated, according to Ansible’s documentation, loops are the recommended way of introducing looping constructs in your code.
Loops provide much more flexibility than with_items for tasks outside of simple lists. They can also be used with other Ansible filters and plugins, such as dictionaries, combining lists, conditionals, nested loops, and more.
Below is a comparison example of using with_items to create users from a simple list and another example of using loops to create users with specific UIDs assigned to each user.
Using with_items:
---
- name: Create users with with_items
hosts: myhosts
become: yes
tasks:
- name: Create multiple users
user:
name: "{{ item }}"
state: present
shell: /bin/bash
with_items:
- alice
- bob
- charlie
Using loops:
Here, item.0 represents the user names and item.1 represents the UID values in the zip filter, assigning mjordan a UID of 1001 and so on.
---
- name: Create users with loop and set specific UIDs
hosts: myhosts
become: yes
tasks:
- name: Create multiple users with specific UIDs
user:
name: "{{ item.0 }}"
uid: "{{ item.1 }}"
state: present
shell: /bin/bash
loop: "{{ ['mjordan', 'mmathers', 'pparker'] | zip([1001, 1002, 1003]) | list }}"
How to use Register Variables with Loops ?
The register keyword can also capture the output of a task that loops. The following example shows the structure of the register variable from a task that loops:
---
- name: Ansible loop 4th playbook to use Register Variables with Loops
gather_facts: no
hosts: localhost
tasks:
- name: Looping Echo Task
shell: "echo This is my item: {{ item }}"
loop:
- one
- two
register: echo_results
- name: Show echo_results varibale
debug:
msg: "{{ echo_results }}"
The echo_results variable is registered.
The contents of the echo_results variable are displayed to the screen.
PLAY [Ansible loop 4th playbook to use Register Variables with Loops] ***************************************
TASK [Looping Echo Task] ************************************************************************************
changed: [localhost] => (item=one)
changed: [localhost] => (item=two)
TASK [Show echo_results varibale] ***************************************************************************
ok: [localhost] => {
"msg": {
"changed": true,
"msg": "All items completed",
"results": [
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "echo This is my item: one",
"delta": "0:00:00.001364",
"end": "2026-01-18 14:16:09.914454",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "echo This is my item: one",
"_uses_shell": true,
"argv": null,
"chdir": null,
"cmd": null,
"creates": null,
"executable": null,
"expand_argument_vars": true,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true
}
},
The { character indicates that the start of the echo_results variable is composed of key-value pairs.
The results key contains the results from the previous task. The [ character indicates the start of a list.
The start of task metadata for the first item (indicated by the item key). The output of the echo command is found in the stdout key.
The start of task result metadata for the second item.
The ] character indicates the end of the results list.
In the above, the results key contains a list. Below, the playbook is modified such that the second task iterates over this list:
vagrant@vm1:~/ansible_lab$ cat loop4.yml
---
- name: Ansible loop 4th playbook to use Register Variables with Loops
gather_facts: no
hosts: localhost
tasks:
- name: Looping Echo Task
shell: "echo This is my item: {{ item }}"
loop:
- one
- two
register: echo_results
- name: Show echo_results varibale
debug:
msg: "STDOUT from previous task: {{ item.stdout }}"
loop: "{{ echo_results['results'] }}"
After executing the above playbook, the output is:
TASK [Looping Echo Task] ************************************************************************************
changed: [localhost] => (item=one)
changed: [localhost] => (item=two)
TASK [Show echo_results varibale] ***************************************************************************
ok: [localhost] => (item={'changed': True, 'stdout': 'This is my item: one', 'stderr': '', 'rc': 0, 'cmd': 'echo This is my item: one', 'start': '2026-01-18 14:26:45.373449', 'end': '2026-01-18 14:26:45.374703', 'delta': '0:00:00.001254', 'msg': '', 'invocation': {'module_args': {'_raw_params': 'echo This is my item: one', '_uses_shell': True, 'expand_argument_vars': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'cmd': None, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['This is my item: one'], 'stderr_lines': [], 'failed': False, 'item': 'one', 'ansible_loop_var': 'item'}) => {
"msg": "STDOUT from previous task: This is my item: one"
}
ok: [localhost] => (item={'changed': True, 'stdout': 'This is my item: two', 'stderr': '', 'rc': 0, 'cmd': 'echo This is my item: two', 'start': '2026-01-18 14:26:45.491699', 'end': '2026-01-18 14:26:45.492886', 'delta': '0:00:00.001187', 'msg': '', 'invocation': {'module_args': {'_raw_params': 'echo This is my item: two', '_uses_shell': True, 'expand_argument_vars': True, 'stdin_add_newline': True, 'strip_empty_ends': True, 'cmd': None, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['This is my item: two'], 'stderr_lines': [], 'failed': False, 'item': 'two', 'ansible_loop_var': 'item'}) => {
"msg": "STDOUT from previous task: This is my item: two"
}
Conditional loops in Ansible
Ansible can use conditionals to execute tasks or plays when certain conditions are met. For example, a conditional can be used to determine available memory on a managed host before Ansible installs or configures a service. Conditionals allow administrators to differentiate between managed hosts and assign them functional roles based on the conditions that they meet. Playbook variables, registered variables, and Ansible facts can all be tested with conditionals. Operators to compare strings, numeric data, and Boolean values are available.
The when statement is used to run a task conditionally. It takes as a value the condition to test. If the condition is met, the task runs. If the condition is not met, the task is skipped.
The following scenarios illustrate the use of conditionals in Ansible:
• A hard limit can be defined in a variable (for example, min_memory) and compared against the available memory on a managed host.
• The output of a command can be captured and evaluated by Ansible to determine whether or not a task completed before taking further action. For example, if a program fails, then a batch is skipped.
• Use Ansible facts to determine the managed host network configuration and decide which template file to send (for example, network bonding or trunking).
• The number of CPUs can be evaluated to determine how to properly tune a web server.
• Compare a registered variable with a predefined variable to determine if a service changed. For example, test the MD5 checksum of a service configuration file to see if the service is changed.
Here is an example of using the conditional loops with the when statement to retrieve the OS of the machine you are running against using ansible_facts, enable nginx service and disable httpd service on Debian-based OS:
- name: Manage Services on Debian OS
hosts: myhosts
vars:
services:
- name: nginx
enabled: true
- name: httpd
enabled: false
tasks:
- name: Ensure services are enabled or disabled accordingly
service:
name: "{{ item.name }}"
enabled: "{{ item.enabled }}"
loop: "{{ services }}"
when: ansible_facts['os_family'] == "Debian" and item.enabled
How to use Multiple Conditions in Ansible ?
One when statement can be used to evaluate multiple conditionals. To do so, conditionals can be combined with either the and or or keywords, and grouped with parentheses.
The following snippets show some examples of how to express multiple conditions.
• If a conditional statement should be met when either condition is true, then you should use the or statement. For example, the following condition is met if the machine is running either
Red Hat Enterprise Linux or Fedora:
when: ansible_distribution == “RedHat” or ansible_distribution == “Fedora”
• With the and operation, both conditions have to be true for the entire conditional statement to be met. For example, the following condition is met if the remote host is a Red Hat Enterprise Linux 7.5 host, and the installed kernel is the specified version:
when: ansible_distribution_version == "7.5" and ansible_kernel =="3.10.0-327.el7.x86_64"
The when keyword also supports using a list to describe a list of conditions. When a list is provided to the when keyword, all of the conditionals are combined using the and operation.
The example below demonstrates another way to combine multiple conditional statements using the and operator:
when:
- ansible_distribution_version == "7.5"
- ansible_kernel == "3.10.0-327.el7.x86_64"
This format improves readability, a key goal of well-written Ansible Playbooks.
COMBINING LOOPS AND CONDITIONAL TASKS
You can combine loops and conditionals.
In the following example, the mariadb-server package is installed by the apt module if there is a file system mounted on / with more than 20 MB free. The ansible_mounts fact is a list of dictionaries, each one representing facts about one mounted file system. The loop iterates over each dictionary in the list, and the conditional statement is not met unless a dictionary is found representing a mounted file system where both conditions are true.
---
- name: ansible 7th playbook to user boht loop and condition
hosts: remote_hosts
tasks:
- name: install net-tools if enough space on root
apt:
name: net-tools
state: latest
loop: "{{ ansible_mounts}}"
when: item.mount == "/" and item.size_available > 20000000
IMPORTANT: When you use when with loop for a task, the when statement is checked for each item.
Here is another example that combines conditionals and register variables. The following
annotated playbook restarts the httpd service only if the postfix service is running:
---
- name: Restart HTTPD if Postfix is Running
hosts: all
tasks:
- name: Get Postfix server status
command: /usr/bin/systemctl is-active postfix
ignore_errors: yes
register: result
- name: Restart Apache HTTPD based on Postfix status
service:
name: httpd
state: restarted
- Is Postfix running or not?
- If it is not running and the command fails, do not stop processing.
- Saves information on the module’s result in a variable named result.
- Evaluates the output of the Postfix task. If the exit code of the systemctl command is 0,then Postfix is active and this task restarts the httpd service.
You can refer also Ansible public documentation page https://docs.ansible.com/projects/ansible/latest/playbook_guide/playbooks_loops.html to get more information about Ansible Loop
Please visit our official website https://linuxgktech.com/ansible/ to know more about Ansible
