Skip to content
Ansible — Linear vs Free Strategy: Free is NOT FAST and Linear is NOT SLOW

Ansible — Linear vs Free Strategy: Free is NOT FAST and Linear is NOT SLOW

August 29, 2026·anupam
anupam

Everyone says free strategy is fast in Ansible than linear — 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.

inventory.yaml
servers:
  hosts:
    ubuntu:
      ansible_host: 10.0.0.1
      ansible_user: alice
    fedora:
      ansible_host: 10.0.0.2
      ansible_user: alice

Two 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.

linear.yaml
- 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.yaml

Note

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=0

Result

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 wait

fedora doesn’t wait for ubutnu to complete it’s Task1. fedora directly moves to Task2 as soon as it completes it’s own Task1.

free.yaml
- 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.yaml
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.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=0

Result

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: 10 tasks * 10 seconds = 100 seconds to complete because of the slowest
  • With linear strategy: Fast hosts complete tasks within ~30 seconds (3 * 10) BUT the overall wall-clock time is still ~100 because 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.541s

Time Taken By Free Strategy

real    0m12.793s
user    0m2.405s
sys     0m0.452s

You 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.819s

Free:

real    0m32.485s
user    0m4.390s
sys     0m0.674s

Linear 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 > file is a simulation of Fedora taking short time for a task to complete
  • sleep 20 && echo hi > file is a simulation of ubuntu taking 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 fedora is done but waiting till ubuntu is — Don’t assume fedora will 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. redo runs 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

Last updated on