Introduction

Ansible playbooks can be slow when managing hundreds of hosts. The good news: a few configuration changes can cut execution time by 50-75%. This guide covers every optimization technique from quick wins to advanced strategies.

Quick Wins (ansible.cfg)

# ansible.cfg — performance settings
[defaults]
# Run on more hosts in parallel (default: 5)
forks = 50

# Smart fact gathering (cache results)
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible-facts
fact_caching_timeout = 86400

# Disable retry files
retry_files_enabled = False

[ssh_connection]
# SSH pipelining — biggest single improvement (~2x speed)
pipelining = True

# Persistent SSH connections
ssh_args = -C -o ControlMaster=auto -o ControlPersist=600s

# Transfer method
transfer_method = smart

Impact Benchmarks

OptimizationTypical Speedup
pipelining = True40-60% faster
forks = 50 (from 5)5-10x faster on many hosts
gathering = smart20-30% faster (cached facts)
ControlPersist=600s10-20% faster (reuse SSH)
Disable gather_facts2-5s saved per play

SSH Pipelining

The single biggest improvement. Reduces SSH operations from 5+ per task to 1:

[ssh_connection]
pipelining = True

Requirement: requiretty must be disabled in /etc/sudoers:

- name: Disable requiretty for pipelining
  ansible.builtin.lineinfile:
    path: /etc/sudoers
    regexp: "^Defaults.*requiretty"
    line: "Defaults !requiretty"
    validate: "visudo -cf %s"

Fact Caching

Don't gather facts every run:

[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible-facts
fact_caching_timeout = 86400    # 24 hours

Or skip facts entirely when not needed:

- hosts: webservers
  gather_facts: false    # Saves 2-5 seconds
  tasks: ...

Optimize Task Design

Combine Package Installs

# SLOW — 5 separate apt calls
- apt: { name: nginx }
- apt: { name: redis }
- apt: { name: git }
- apt: { name: curl }
- apt: { name: vim }

# FAST — 1 apt call
- ansible.builtin.apt:
    name: [nginx, redis, git, curl, vim]
    state: present
    update_cache: true
    cache_valid_time: 3600

Use async for Long Tasks

# Run long tasks in parallel
- name: Update packages (all hosts simultaneously)
  ansible.builtin.apt:
    upgrade: dist
  async: 3600
  poll: 0
  register: update_job

# Do other work...

- name: Wait for updates
  ansible.builtin.async_status:
    jid: "{{ update_job.ansible_job_id }}"
  until: result.finished
  retries: 120
  delay: 30

Use free Strategy

# Let each host proceed independently
- hosts: webservers
  strategy: free    # Don't wait for slowest host
  tasks: ...

Profile Tasks

Find what's slow:

[defaults]
callbacks_enabled = timer, profile_tasks, profile_roles

Output:

===============================================================================
Install packages ------------------------------------------ 45.23s
Gather facts --------------------------------------------- 12.11s
Deploy configuration -------------------------------------- 3.45s

Reduce SSH Round-Trips

Use Modules Over command/shell

# SLOW — multiple SSH operations
- command: mkdir -p /opt/myapp
- command: chown deploy:deploy /opt/myapp
- command: chmod 755 /opt/myapp

# FAST — single module call
- ansible.builtin.file:
    path: /opt/myapp
    state: directory
    owner: deploy
    mode: "0755"

Batch template/copy Operations

# SLOW — multiple file transfers
- copy: { src: file1, dest: /etc/myapp/ }
- copy: { src: file2, dest: /etc/myapp/ }
- copy: { src: file3, dest: /etc/myapp/ }

# FASTER — synchronize entire directory
- ansible.posix.synchronize:
    src: files/myapp/
    dest: /etc/myapp/

Mitogen (Advanced)

Mitogen replaces Ansible's SSH-based execution with an optimized connection strategy — 2-7x faster:

pip install mitogen
[defaults]
strategy_plugins = /path/to/mitogen/ansible_mitogen/plugins/strategy
strategy = mitogen_linear

⚠️ Check compatibility with your Ansible version.

Scale to 1000+ Hosts

[defaults]
forks = 100
gathering = smart
fact_caching = redis
fact_caching_connection = localhost:6379:0
fact_caching_timeout = 86400

[ssh_connection]
pipelining = True
ssh_args = -C -o ControlMaster=auto -o ControlPersist=600s -o PreferredAuthentications=publickey

[inventory]
cache = True
cache_plugin = jsonfile
cache_timeout = 3600

Serial Batches for Large Fleets

- hosts: all
  serial: "20%"     # 20% of hosts at a time
  tasks: ...

Pull Mode

For 1000+ hosts, consider ansible-pull — each host pulls and applies its own config:

# On each host (via cron)
ansible-pull -U https://github.com/org/ansible-repo.git site.yml

Optimization Checklist

#OptimizationEffortImpact
1Enable pipelining1 min⭐⭐⭐⭐⭐
2Increase forks1 min⭐⭐⭐⭐
3Enable fact caching2 min⭐⭐⭐
4SSH ControlPersist1 min⭐⭐⭐
5Combine package installs5 min⭐⭐⭐
6Skip gather_facts1 min⭐⭐
7Use free strategy1 min⭐⭐
8Profile tasks2 min⭐⭐
9async for long tasks10 min⭐⭐
10Mitogen15 min⭐⭐⭐⭐

Troubleshooting

Still Slow After Optimizations

# Profile to find the bottleneck
ANSIBLE_CALLBACKS_ENABLED=profile_tasks ansible-playbook site.yml

# Check SSH connection time
time ssh user@host "echo ok"

# Check DNS resolution (common hidden slowdown)
time nslookup myhost.example.com

Pipelining Not Working

# Verify pipelining is active
ansible-config dump | grep PIPELINING
# Should show: ANSIBLE_PIPELINING = True

Conclusion

The three biggest wins: SSH pipelining (40-60% faster), increase forks (5-10x on many hosts), and fact caching (20-30% faster). Apply these three changes and you'll see dramatic improvement immediately. For further gains, profile your tasks to find bottlenecks, combine operations, use async for long tasks, and consider Mitogen for 2-7x speedup. At scale (1000+ hosts), switch to pull mode and use Redis for fact caching.