Last updated: October 1, 2026 · Reading time: 4–6 minutes
With an Ansible loop, you can run a task for each item in a list. Here you’ll find the basics, along with practical examples, and learn how to use ` include_tasks ` to loop through multiple tasks that depend on one another.
The most important points in 30 seconds
- An Ansible loop executes a task for each element in a list, wher
{{ item }}represents the current element. - The list can be included directly in the task, come from a variable, or consist of dictionaries with multiple values per iteration.
whenis evaluated individually in a loop on each iteration.- For multiple interdependent tasks, save them in a separate file and append the ”
loop” toinclude_tasks. - Having your own
loop_varvialoop_controlprevents name conflicts withitem.
The loop is one of the most useful tools in Ansible. It ensures that a task is executed multiple times—once for each item in a list. This reduces paperwork and makes playbooks much easier to maintain. This article first covers the basics and then delves into an advanced use case: looping through an entire chain of related tasks.
Using an Ansible Loop in a Single Task
Loop is most commonly used to call a single module multiple times with different values.
- name: Mehrere Pakete installieren
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- curlThis is equivalent to three separate calls to the apt module. Within the loop, it refers to {{ item }} on the current element.
Loop Using a Variable
You can also retrieve the list from a variable instead of writing it out directly:
vars:
paketliste:
- nginx
- git
- curl
tasks:
- name: Pakete installieren
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop: "{{ paketliste }}"Loop through a list of dictionaries
This is very commonly used when multiple values are needed per iteration:
- name: Nutzer anlegen
ansible.builtin.user:
name: "{{ item.name }}"
uid: "{{ item.uid }}"
groups: "{{ item.groups }}"
loop:
- { name: "alice", uid: 1001, groups: "admins" }
- { name: "fabian", uid: 1002, groups: "finance" }Conditions in Ansible Loops
“when ” is evaluated separately for each iteration:
- name: Nur bestimmte Pakete installieren
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- git
- docker
when: item != "docker"Ansible Loops Across Multiple Tasks: Multiple Interrelated Loops
A simple loop is ideal when a single task needs to be executed multiple times. It gets more complicated when several tasks depend on one another. For example, a task may need the result (register) of a previous task for a when condition.
A typical example of this is setting up multiple websites. For each website, a directory should be created, a vHost configuration should be set up, and the website should be activated as soon as the vHost file has been modified.
- name: "create {{ site }} directory"
ansible.builtin.file:
path: "/var/www/{{ site }}"
state: directory
- name: "create {{ site }}"
ansible.builtin.template:
src: vhost.j2
dest: "/etc/apache2/sites-available/{{ site }}"
register: vhost
- name: "enable {{ site }}"
ansible.builtin.command: /usr/sbin/a2ensite "{{ site }}"
register: result
when: vhost.changed
changed_when: "'Enabling site' in result.stdout"
notify: apache_reloadIf you were to use a loop to process each task individually here, you would have to correctly map the respective register results to the individual iterations. That makes the code unnecessarily complicated.

The Solution: `include_tasks` with a loop
This is where `include_tasks` comes into play. This allows you to move several related tasks to a separate file.
The `include_tasks` task itself is then placed inside the `loop` loop. This causes the entire task file to be executed once for each item on the list.
Here’s what the main playbook looks like:
- ansible.builtin.set_fact:
sites:
- default
- icingaweb2
- name: create vhosts
ansible.builtin.include_tasks: create-vhosts.yml
loop: "{{ sites }}"
loop_control:
loop_var: siteUsing a custom `loop_var` name (`site` instead of the default `item`) ensures that no naming conflicts occur if the included file eventually gets its own loop with `item`.
The create-vhosts.yml task file
The other tasks are stored in create-vhosts.yml:
- name: "create {{ site }} directory"
ansible.builtin.file:
path: "/var/www/{{ site }}"
state: directory
- name: "create {{ site }}"
ansible.builtin.template:
src: vhost.j2
dest: "/etc/apache2/sites-available/{{ site }}"
register: vhost
- name: "enable {{ site }}"
ansible.builtin.command: /usr/sbin/a2ensite "{{ site }}"
register: result
when: vhost.changed
changed_when: "'Enabling site' in result.stdout"
notify: apache_reloadThe result in the Playbook output
The result: All three tasks are executed in full for every element in Sites, including the correctly assigned register context.
TASK [set_fact] *********************************************
ok: [localhost]
TASK [create vhosts] ****************************************
included: /Users/dwening/Documents/netways/ansible_test/create-vhosts.yml for localhost => (item=default)
included: /Users/dwening/Documents/netways/ansible_test/create-vhosts.yml for localhost => (item=icingaweb2)
TASK [create default directory] *****************************
ok: [localhost]
TASK [create default] ***************************************
ok: [localhost]
TASK [enable default] ***************************************
ok: [localhost]
TASK [create icingaweb2 directory] **************************
ok: [localhost]
TASK [create icingaweb2] ************************************
ok: [localhost]
TASK [enable icingaweb2] ************************************
ok: [localhost]
PLAY RECAP **************************************************
localhost : ok=10 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Conclusion: `include_tasks` makes entire task sequences loopable
Loops are easy to explain in their simplest form: a list, a task, and multiple iterations. However, as soon as multiple tasks with dependencies need to be repeated, simply looping through the task is no longer sufficient. The combination of `include_tasks` and `loop` fills exactly this gap. It treats an entire task sequence as a single unit and makes it repeatable without having to duplicate code or use Jinja2 in `when` conditions.
Do you want to use Ansible in your environment or clean up existing playbooks? Our consultants will be happy to help you with that!
