Ansible Performance Tuning: Speed Up Playbooks 10x
By Luca Berton · Published 2024-01-01 · Category: installation
Complete Ansible performance tuning guide. Speed up playbooks with pipelining, increased forks, fact caching, async tasks, free strategy, Mitogen, and profile.
Default Ansible settings prioritize safety over speed. With the right tuning, you can make playbooks 2-10x faster without sacrificing reliability.
Quick Wins (ansible.cfg)
[defaults]
forks = 50 # Default: 5 — process 50 hosts in parallel
gathering = smart # Only gather facts if not cached
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 7200 # Cache facts for 2 hours
stdout_callback = yaml # Better readability
callbacks_enabled = timer, profile_tasks
[ssh_connection]
pipelining = true # Biggest single speedup
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
This alone can 3-5x your playbook speed.
See also: 10 Proven Methods to Optimize Ansible Playbook Performance
1. Pipelining (Biggest Single Win)
Without pipelining, every task requires multiple SSH round-trips:
Without pipelining (default):
SSH → create temp dir → SSH → upload module → SSH → execute → SSH → cleanup
= 4+ SSH operations per task
With pipelining:
SSH → pipe module through stdin → execute
= 1 SSH operation per task
[ssh_connection]
pipelining = true
Requirement: Target hosts must NOT have requiretty in sudoers:
# On target, remove/comment this line in /etc/sudoers:
# Defaults requiretty
Speedup: 2-4x for playbooks with many tasks.
2. Increase Forks
[defaults]
forks = 50 # Default is 5
The forks setting controls how many hosts are processed in parallel. Set it to at least the number of hosts you typically manage.
| Hosts | Recommended Forks | |-------|------------------| | 1-10 | 10 | | 10-50 | 50 | | 50-200 | 100 | | 200+ | 200-500 (depends on controller CPU/RAM) |
Rule of thumb: Each fork uses ~50MB of RAM on the controller.
See also: Ansible Performance Optimization: Speed Up Playbooks for Large-Scale Environments
3. SSH Multiplexing
[ssh_connection]
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s
• -C: Compress SSH traffic
• ControlMaster=auto: Reuse SSH connections
• ControlPersist=60s: Keep connection open for 60 seconds
Speedup: 20-50% fewer SSH handshakes.
4. Fact Caching
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 7200
Fact gathering takes 2-5 seconds per host. With smart gathering and caching, facts are only gathered on first run.
Redis Caching (Multi-User)
[defaults]
fact_caching = redis
fact_caching_connection = localhost:6379
fact_caching_timeout = 7200
Disable Facts When Not Needed
- hosts: all
gather_facts: false # Skip entirely if no facts needed
tasks:
- ansible.builtin.ping:
Partial Fact Gathering
- hosts: all
gather_facts: true
gather_subset:
- network # Only gather network facts
- '!hardware' # Skip slow hardware detection
- '!ohai'
- '!facter'
See also: Manage Disk Space by Cleaning /var/log/journal on Fedora
5. Free Strategy
Default linear strategy waits for all hosts to complete each task. free lets fast hosts proceed:
- hosts: webservers
strategy: free
tasks:
- name: Install packages
ansible.builtin.apt:
name: nginx
state: present
# Fast hosts don't wait for slow ones
⚠️ Caveat: Output is interleaved and harder to read. Don't use with serial or when task order across hosts matters.
6. Async Tasks
Run long tasks in the background:
- name: Run long-running update
ansible.builtin.apt:
upgrade: dist
async: 3600 # Max runtime: 1 hour
poll: 0 # Don't wait — fire and forget
- name: Do other work while update runs
ansible.builtin.copy:
src: config.conf
dest: /etc/myapp/
- name: Wait for update to finish
ansible.builtin.async_status:
jid: "{{ update_result.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 30
7. Mitogen Plugin
Mitogen replaces Ansible's SSH/module execution with a faster transport:
pip install mitogen
# ansible.cfg
[defaults]
strategy_plugins = /path/to/mitogen/ansible_mitogen/plugins/strategy
strategy = mitogen_linear
Speedup: 2-7x depending on workload. Biggest gains on playbooks with many small tasks.
⚠️ Compatibility: Doesn't support all connection types. Test thoroughly.
8. Reduce Task Count
Package Lists Instead of Loops
# ❌ SLOW — one SSH round-trip per package
- name: Install packages
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- postgresql
- redis
# ✅ FAST — single apt transaction
- name: Install packages
ansible.builtin.apt:
name:
- nginx
- postgresql
- redis
state: present
Combine Related Tasks
# ❌ SLOW — three separate SSH operations
- ansible.builtin.file:
path: /opt/app
state: directory
- ansible.builtin.file:
path: /opt/app/logs
state: directory
- ansible.builtin.file:
path: /opt/app/config
state: directory
# ✅ FAST — one SSH operation with loop
- ansible.builtin.file:
path: "{{ item }}"
state: directory
loop:
- /opt/app
- /opt/app/logs
- /opt/app/config
9. Profile Your Playbooks
[defaults]
callbacks_enabled = timer, profile_tasks, profile_roles
PLAY RECAP ********
Tuesday 13 April 2026 01:00:00 +0000 (0:00:02.345) 0:01:23.456 ****
===============================================================================
Install packages ------------------------------------------ 45.23s
Gather facts --------------------------------------------- 12.56s
Deploy configuration -------------------------------------- 8.12s
Restart services ------------------------------------------ 3.45s
Now you know where to optimize.
10. Limit Scope
# Only run on specific hosts
ansible-playbook site.yml --limit web01,web02
# Only run specific tags
ansible-playbook site.yml --tags deploy
# Start at a specific task
ansible-playbook site.yml --start-at-task "Deploy application"
Production ansible.cfg
[defaults]
forks = 50
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 7200
stdout_callback = yaml
callbacks_enabled = timer, profile_tasks
host_key_checking = True
retry_files_enabled = False
any_errors_fatal = False
[ssh_connection]
pipelining = True
ssh_args = -C -o ControlMaster=auto -o ControlPersist=60s -o ServerAliveInterval=30
control_path_dir = ~/.ansible/cp
control_path = %(directory)s/%%h-%%r-%%p
[privilege_escalation]
become = True
become_method = sudo
Benchmark Results
Typical speedups on a 50-host playbook (20 tasks):
| Optimization | Time | Speedup | |-------------|------|---------| | Default settings | 25 min | 1x | | + Pipelining | 12 min | 2.1x | | + Forks 50 | 5 min | 5x | | + Fact caching | 3.5 min | 7.1x | | + Mitogen | 2 min | 12.5x |
FAQ
What's the single most impactful optimization?
Pipelining. It reduces SSH round-trips from 4+ to 1 per task. Combined with increased forks, you'll see 3-5x speedup immediately.
Will increasing forks overload my controller?
Each fork uses ~50MB RAM. 50 forks = ~2.5GB. Monitor your controller's CPU and RAM. If you hit limits, increase forks gradually and watch for SSH connection errors.
Is Mitogen safe for production?
Mitogen is stable but not officially supported by Red Hat. It works well for most use cases but may have edge cases with certain connection plugins or modules. Test thoroughly in staging first.
Why is gather_facts so slow?
It runs the setup module which collects hardware, network, OS, and package info. Use gather_subset to skip expensive checks (hardware detection is the slowest). Or cache facts and use gathering = smart.
Conclusion
Enable pipelining (2-4x), increase forks to match your host count (2-10x), enable fact caching (skip redundant gathering), and profile your playbooks to find bottlenecks. These four changes alone will make most playbooks 5-10x faster.
Related Articles
• Ansible serial Rolling Updates Guide • Ansible Check Mode / Dry Run Guide • How to Make Playbooks Idempotent • Ansible Inventory GuideCategory: installation