Ansible — Linear vs Free Strategy: Free is NOT FAST and Linear is NOT SLOW
Everyone says
freestrategy is fast in Ansible thanlinear— but here is the real story — observed one.
I keep hearing that free strategy is fast in Ansible than the default linear strategy. And let me reveal my bad habit — I don’t trust what I don’t see. Therefore, I, after some hearings decided to check it myself. So, let’s go together in this observation.
Inventory File: The Same Throughout
The inventory file will remain the same across strategies. If you want to follow along, you need your own setup.
servers:
hosts:
ubuntu:
ansible_host: 10.0.0.1
ansible_user: alice
fedora:
ansible_host: 10.0.0.2
ansible_user: aliceTwo servers: ubuntu and fedora to be logged in as user alice.
Playbook: Linear (Default) Strategy
Here is how linear strategy works:
Ansible starts tasks on each hosts and only moves to the next host when all hosts are done with their current task — even if a host is done with it’s task, it needs to wait till others complete their tasks before starting the next task in the queue.
This means:
Time -->
ubuntu: [Task 1.......] [Task 2] [done]
fedora: [Task 1] [wait] [Task 2] [done]
^wait for ubuntu Even if Task 1 completes fast in fedora, it waits for ubuntu to complete before moving to the next Task2.
- hosts: servers
strategy: linear # default
tasks:
- name: Sleep depending on OS
ansible.builtin.command: "sleep {{ 5 if ansible_facts['distribution'] == 'Fedora' else 10 }}"
- name: Simple Echo HI Task
ansible.builtin.command: "echo HI"ansible-playbook -i inventory.yaml --key-file lab-setup/alice linear.yamlNote
I need to pass --key-file because I have no config file setup and I haven’t use ssh-agent. You need not pass if you have setup agent or have explicitly mentioned in configuration.
PLAY [servers] *************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************
ok: [ubuntu]
ok: [fedora]
TASK [Sleep depending on OS] *******************************************************************************************************************
changed: [fedora] => {"changed": true, "cmd": ["sleep", "5"], "delta": "0:00:04.207544", "end": "2026-08-28 20:18:36.408661", "msg": "", "rc": 0, "start": "2026-08-28 20:18:32.201117", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
changed: [ubuntu] => {"changed": true, "cmd": ["sleep", "10"], "delta": "0:00:09.221329", "end": "2026-08-28 20:18:41.359071", "msg": "", "rc": 0, "start": "2026-08-28 20:18:32.137742", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Simple Echo HI Task] *********************************************************************************************************************
changed: [ubuntu] => {"changed": true, "cmd": ["echo", "HI"], "delta": "0:00:00.007217", "end": "2026-08-28 20:18:41.728880", "msg": "", "rc": 0, "start": "2026-08-28 20:18:41.721663", "stderr": "", "stderr_lines": [], "stdout": "HI", "stdout_lines": ["HI"]}
changed: [fedora] => {"changed": true, "cmd": ["echo", "HI"], "delta": "0:00:00.005720", "end": "2026-08-28 20:18:41.839005", "msg": "", "rc": 0, "start": "2026-08-28 20:18:41.833285", "stderr": "", "stderr_lines": [], "stdout": "HI", "stdout_lines": ["HI"]}
PLAY RECAP *************************************************************************************************************************************
fedora : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ubuntu : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Result
You can see fedora host waits (because it completes it’s first task first) till ubuntu host completes it’s first task (Simple Echo…) before moving to the next task (Sleep…) in the queue — as described above.
Playbook: Free Strategy
Here is how free strategy works:
Ansible starts tasks on each hosts and each host moves to the next task as soon as it completes it’s current task — any host that has completed it’s task doesn’t wait for others just to start it’s another task in the queue.
This means:
Time -->
ubuntu: [Task 1.......] [Task 2] [done]
fedora: [Task 1] [Task 2] [done]
^doesn't waitfedora doesn’t wait for ubutnu to complete it’s Task1. fedora directly moves to Task2 as soon as it completes it’s own Task1.
- hosts: servers
strategy: free
tasks:
- name: Sleep depending on OS
ansible.builtin.command: "sleep {{ 5 if ansible_facts['distribution'] == 'Fedora' else 10 }}"
- name: Simple Echo HI Task
ansible.builtin.command: "echo HI"ansible-playbook -i inventory.yaml --key-file lab-setup/alice free.yamlPLAY [servers] *************************************************************************************************************************************
TASK [Gathering Facts] *************************************************************************************************************************
ok: [ubuntu]
ok: [fedora]
TASK [Sleep depending on OS] *******************************************************************************************************************
changed: [fedora] => {"changed": true, "cmd": ["sleep", "5"], "delta": "0:00:04.204761", "end": "2026-08-28 20:19:04.557970", "msg": "", "rc": 0, "start": "2026-08-28 20:19:00.353209", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Simple Echo HI Task] *********************************************************************************************************************
changed: [fedora] => {"changed": true, "cmd": ["echo", "HI"], "delta": "0:00:00.004429", "end": "2026-08-28 20:19:05.043136", "msg": "", "rc": 0, "start": "2026-08-28 20:19:05.038707", "stderr": "", "stderr_lines": [], "stdout": "HI", "stdout_lines": ["HI"]}
TASK [Sleep depending on OS] *******************************************************************************************************************
changed: [ubuntu] => {"changed": true, "cmd": ["sleep", "10"], "delta": "0:00:09.208476", "end": "2026-08-28 20:19:09.455139", "msg": "", "rc": 0, "start": "2026-08-28 20:19:00.246663", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
TASK [Simple Echo HI Task] *********************************************************************************************************************
changed: [ubuntu] => {"changed": true, "cmd": ["echo", "HI"], "delta": "0:00:00.005766", "end": "2026-08-28 20:19:09.817536", "msg": "", "rc": 0, "start": "2026-08-28 20:19:09.811770", "stderr": "", "stderr_lines": [], "stdout": "HI", "stdout_lines": ["HI"]}
PLAY RECAP *************************************************************************************************************************************
fedora : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ubuntu : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0Result
You can see fedora host moves to second task (Simple Echo…) as soon as it completes the first task (Sleep…) — exactly the same behavior described above.
The Real Observation
Say you have 10 hosts and 10 tasks and most hosts complete their task within 3 seconds but there are also too slow hosts talking 10 seconds per task:
- With linear stratey:
10tasks *10seconds =100seconds to complete because of the slowest - With linear strategy: Fast hosts complete tasks within
~30seconds (3*10) BUT the overall wall-clock time is still~100because this is what it takes all the tasks(plays) to complete — you can’t deny that.
Yep that’s what it is. You can’t simply say free is FAST. free is only fast per host NOT FAST PER ALL PLAYS — wall-clock time is nearly the same. I actually run the above playbooks with time command:
Time Taken By Linear Strategy
real 0m13.001s
user 0m1.940s
sys 0m0.541sTime Taken By Free Strategy
real 0m12.793s
user 0m2.405s
sys 0m0.452sYou may say “12.793 < 13.0011 (free wins) which is great on just 10 second task” but it’s just a sample which can’t be universal. In fact, I increase sleep to 15 and 30 and see what:
Linear:
real 0m32.387s
user 0m2.562s
sys 0m0.819sFree:
real 0m32.485s
user 0m4.390s
sys 0m0.674sLinear wins (32.387 < 32.485). NO!, it’s just what it is (OS resource management maybe). The next run may take less time.
The point is never about which is fast. The point is that the wall-clock time is nearly the same — what makes them different is linear strategy waits for each host to complete before moving on while free strategy moves as soon as it completes.
Not To Confuse With
Let’s say, fedora takes 10 seconds and ubuntu takes 20 seconds` for a task the complete:
---
- name: Short vs Long Task Depending on OS
ansible.builtin.shell:
cmd: >-
{{ 'sleep 10 && echo hi > file'
if ansible_facts['distribution'] == 'Fedora'
else 'sleep 20 && echo hi > file' }} sleep 10 && echo hi > fileis a simulation ofFedorataking short time for a task to completesleep 20 && echo hi > fileis a simulation ofubuntutaking longer time for the same task to complete
If something causes the Ansible playbook process to cease (server conn reset, OS signal interruption, etc) after
fedorais done but waiting tillubuntuis — Don’t assumefedorawill revert to original state, once the task is completed on the host (changed: [fedora]) — it’s done — tasks are independent (but wait happens), BUT NOT atomic.
Gotcha: free with debugger
In short:
- Linear strategy: All hosts pause while you use the debugger.
redoruns the failed task immediately. - Free strategy: Hosts can progress independently and queue later tasks.
- While the debugger is open, no new tasks run, but already queued tasks remain.
- After exiting the debugger, queued tasks run.
- With
redo, other queued tasks may run before the retried task — could be one of the reasons not to use free strategy when dependency matters.
When To Use And When To Avoid Free Strategy
Use If
- Some hosts are fast and some hosts are slow (hardware, CPU, memory, network could be the factors)
- Tasks are not dependent on other hosts’ failure or success
- Taks have variable execution time (some packages are lightweight on some distros)
Avoid If
- Tasks depend on other host’s status and need synchronization
- Your playbook logic depends on all hosts being at the same task point
References
- Ansible docs — free strategy, playbook strategies
- Thanks to
@nawazdhandala— blog which shapes this blog
