Ansible Async: Run Long-Running Tasks in Background (Complete Guide)
By Luca Berton · Published 2024-01-01 · Category: troubleshooting
Complete guide to Ansible async and poll. Run long-running tasks in the background, implement fire-and-forget patterns, parallel execution, check job status.
The async keyword runs Ansible tasks in the background, allowing long-running operations to continue without blocking the playbook. Combined with poll, it enables fire-and-forget patterns, parallel execution, and timeout control.
How Async Works
- name: Long-running task
ansible.builtin.command: /opt/scripts/build.sh
async: 3600 # Maximum runtime in seconds (1 hour)
poll: 10 # Check every 10 seconds
• async: N — Maximum allowed runtime in seconds. Task fails if it exceeds this.
• poll: N — How often (seconds) to check if the task is done.
• poll: 0 = Fire and forget (don't wait)
• poll: N (N > 0) = Wait and check every N seconds
See also: Ansible Run Playbooks in Parallel: Async, Forks & Concurrent Execution
Basic Patterns
Wait with Polling (Default)
# Runs in background, checks every 30 seconds, max 1 hour
- name: Run database backup
ansible.builtin.command: /opt/scripts/backup-database.sh
async: 3600
poll: 30
Fire and Forget
# Start task and move on immediately
- name: Start long-running data migration
ansible.builtin.command: /opt/scripts/migrate-data.sh
async: 7200
poll: 0
register: migration_job
# ... other tasks run while migration continues ...
# Check status later
- name: Check migration status
ansible.builtin.async_status:
jid: "{{ migration_job.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 120
delay: 60
Parallel Execution
Run Tasks on Multiple Hosts Simultaneously
# Fire and forget on all hosts, then wait for all to finish
- name: Start package update on all hosts
ansible.builtin.yum:
name: '*'
state: latest
async: 1800
poll: 0
register: yum_update
- name: Wait for updates to complete
ansible.builtin.async_status:
jid: "{{ yum_update.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 30
Parallel Tasks on Same Host
# Start multiple independent tasks
- name: Download large file 1
ansible.builtin.get_url:
url: https://example.com/large-file-1.tar.gz
dest: /tmp/file1.tar.gz
async: 600
poll: 0
register: download1
- name: Download large file 2
ansible.builtin.get_url:
url: https://example.com/large-file-2.tar.gz
dest: /tmp/file2.tar.gz
async: 600
poll: 0
register: download2
- name: Start compilation
ansible.builtin.command: make -j4
args:
chdir: /opt/source
async: 1800
poll: 0
register: compile_job
# Wait for all to finish
- name: Wait for downloads and compilation
ansible.builtin.async_status:
jid: "{{ item.ansible_job_id }}"
register: async_results
until: async_results.finished
retries: 60
delay: 15
loop:
- "{{ download1 }}"
- "{{ download2 }}"
- "{{ compile_job }}"
See also: Ansible async and poll: Run Long Tasks Without Timeout Complete Guide
async_status Module
Check on fire-and-forget jobs:
- name: Start background job
ansible.builtin.command: /opt/scripts/process.sh
async: 3600
poll: 0
register: bg_job
- name: Do other work
ansible.builtin.debug:
msg: "Doing other tasks while job runs..."
- name: Check job status
ansible.builtin.async_status:
jid: "{{ bg_job.ansible_job_id }}"
register: job_status
- name: Show status
ansible.builtin.debug:
msg: |
Finished: {{ job_status.finished }}
Started: {{ job_status.started }}
{% if job_status.finished %}
Return code: {{ job_status.rc }}
{% endif %}
# Wait until complete
- name: Wait for job to finish
ansible.builtin.async_status:
jid: "{{ bg_job.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 60
delay: 30
# Checks every 30 seconds, up to 30 minutes
Real-World Patterns
Long Package Updates Without SSH Timeout
# SSH connections may time out during long operations
# async keeps the task running server-side even if SSH disconnects
- name: Upgrade all packages (may take 30+ minutes)
ansible.builtin.yum:
name: '*'
state: latest
async: 3600 # Allow up to 1 hour
poll: 60 # Check every minute
Parallel Service Restarts
- name: Restart services in parallel
ansible.builtin.systemd:
name: "{{ item }}"
state: restarted
async: 120
poll: 0
register: restart_jobs
loop:
- nginx
- php-fpm
- redis
- name: Wait for all restarts
ansible.builtin.async_status:
jid: "{{ item.ansible_job_id }}"
register: restart_results
until: restart_results.finished
retries: 12
delay: 10
loop: "{{ restart_jobs.results }}"
Reboot and Reconnect
- name: Reboot the server
ansible.builtin.command: shutdown -r now
async: 1
poll: 0
ignore_errors: true
- name: Wait for server to come back
ansible.builtin.wait_for_connection:
connect_timeout: 10
sleep: 5
delay: 30
timeout: 300
Background Monitoring During Deployment
- name: Start log monitoring in background
ansible.builtin.shell: "tail -f /var/log/myapp/app.log > /tmp/deploy-log.txt 2>&1 &"
async: 600
poll: 0
register: monitor
- name: Deploy application
ansible.builtin.include_role:
name: deploy
- name: Stop monitoring
ansible.builtin.command: "kill $(cat /tmp/deploy-monitor.pid)"
ignore_errors: true
- name: Show deployment logs
ansible.builtin.command: cat /tmp/deploy-log.txt
register: deploy_logs
changed_when: false
- name: Display logs
ansible.builtin.debug:
var: deploy_logs.stdout_lines
See also: Ansible Jinja2 Filters: Transform Data in Playbooks (Complete Reference)
Timeout Handling
# Task fails if it exceeds async timeout
- name: Process with timeout
ansible.builtin.command: /opt/scripts/process.sh
async: 300 # 5 minute maximum
poll: 10
register: result
ignore_errors: true
- name: Handle timeout
ansible.builtin.debug:
msg: "Process timed out — cleaning up"
when: result is failed
- name: Cleanup on timeout
ansible.builtin.command: /opt/scripts/cleanup.sh
when: result is failed
Limitations
Not all modules support async — Modules that need persistent connections (e.g.,synchronize) may not work
No async with with_items/loop — Each loop iteration runs sequentially; use fire-and-forget + async_status loop instead
Temp files on remote hosts — Async creates ~/.ansible_async/ files that need cleanup
become may not work with poll: 0 — Some privilege escalation methods conflict with backgrounding
FAQ
What does async do in Ansible?
async runs a task in the background with a maximum time limit. It prevents SSH timeouts for long operations and enables fire-and-forget patterns where you start a task and check its status later.
What is the difference between async with poll 0 and poll N?
poll: 0 (fire-and-forget) starts the task and immediately continues to the next task. poll: N (N > 0) starts the task and waits, checking every N seconds until it completes.
How do I run tasks in parallel with Ansible?
Use async with poll: 0 to start multiple tasks without waiting, then use async_status in a loop to wait for all jobs to complete. This runs tasks concurrently instead of sequentially.
What happens when an async task times out?
The task is killed on the remote host and Ansible reports a failure. Use ignore_errors: true if you want to handle timeouts gracefully with cleanup tasks.
Can I use async with loops?
Not directly — each loop iteration is still sequential. Instead, use poll: 0 inside the loop to start all iterations as fire-and-forget jobs, then use async_status with a second loop to wait for all results.
Conclusion
•async: N + poll: M — Run with timeout, check every M seconds
• async: N + poll: 0 — Fire and forget, check later with async_status
• Prevents SSH timeouts for long operations
• Enables parallel execution with fire-and-forget + async_status loops
• Always set a reasonable async timeout to avoid zombie tasks
Related Articles
• Ansible retries & until: Retry Failed Tasks • Ansible wait_for: Wait for Conditions • Ansible serial: Rolling Updates Guide • Ansible Run Playbooks in ParallelCategory: troubleshooting