Introduction
By default, Ansible waits for each task to complete before moving to the next. For long-running tasks (builds, backups, large downloads), async and poll let you run tasks in the background and check on them later.
How It Works
# async: Maximum time (seconds) the task can run
# poll: How often (seconds) to check status
# poll: 0 = fire-and-forget (don't wait)
# Wait with periodic check (default behavior with async)
- name: Long build
ansible.builtin.command: /opt/build.sh
async: 3600 # Max 1 hour
poll: 30 # Check every 30 seconds
# Fire and forget
- name: Start backup
ansible.builtin.command: /opt/backup.sh
async: 7200 # Max 2 hours
poll: 0 # Don't wait
register: backup_job
Check Status Later
# Start task (fire-and-forget)
- name: Start database backup
ansible.builtin.command: /opt/backup-db.sh
async: 3600
poll: 0
register: backup_job
# Do other work while backup runs
- name: Deploy application
ansible.builtin.copy:
src: app.tar.gz
dest: /opt/app/
- name: Configure application
ansible.builtin.template:
src: config.yml.j2
dest: /opt/app/config.yml
# Now check on the backup
- name: Wait for backup to finish
ansible.builtin.async_status:
jid: "{{ backup_job.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 120
delay: 30
Parallel Long Tasks
# Start multiple tasks simultaneously
- name: Start backup on all databases
ansible.builtin.command: "/opt/backup.sh {{ item }}"
async: 3600
poll: 0
register: backup_jobs
loop:
- users_db
- orders_db
- analytics_db
# Wait for ALL to complete
- name: Wait for all backups
ansible.builtin.async_status:
jid: "{{ item.ansible_job_id }}"
register: job_results
until: job_results.finished
retries: 120
delay: 30
loop: "{{ backup_jobs.results }}"
Practical Patterns
Package Updates
# OS updates can take a long time
- name: Update all packages
ansible.builtin.apt:
upgrade: dist
update_cache: true
async: 1800 # 30 min max
poll: 60 # Check every minute
Software Build
- name: Build from source
ansible.builtin.command: make -j{{ ansible_processor_vcpus }} install
args:
chdir: /opt/src/myapp
async: 3600
poll: 30
Download Large Files
- name: Download large dataset
ansible.builtin.get_url:
url: https://releases.example.com/bigfile.tar.gz
dest: /tmp/bigfile.tar.gz
async: 1800
poll: 0
register: download
# Do other setup
- name: Create directories
ansible.builtin.file:
path: /opt/myapp
state: directory
# Wait for download
- name: Wait for download
ansible.builtin.async_status:
jid: "{{ download.ansible_job_id }}"
register: dl_result
until: dl_result.finished
retries: 60
delay: 30
Rolling Restart with Warmup
- name: Restart application
ansible.builtin.service:
name: myapp
state: restarted
# Fire-and-forget warmup
- name: Warm cache
ansible.builtin.command: /opt/myapp/warm-cache.sh
async: 600
poll: 0
# Continue with next host immediately
Reboot and Reconnect
- name: Reboot 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:
delay: 30
timeout: 300
Parameters
| Parameter | Description |
|---|---|
async | Maximum runtime in seconds |
poll | Check interval (0 = fire-and-forget) |
async_status | Module to check job status |
jid | Job ID (from registered variable) |
async_status Return Values
- async_status:
jid: "{{ job.ansible_job_id }}"
register: result
# result.finished → 0 (running) or 1 (done)
# result.started → 1 if started
# result.rc → return code (when finished)
# result.stdout → stdout (when finished)
# result.stderr → stderr (when finished)
Limitations
- Cannot use with
loopon theasync_statuscheck — useloopon the original task - Task must complete within
asyncseconds or it's killed - Not all modules support async — works best with
command,shell,raw - Async tasks survive connection loss — they keep running on the remote host
- Results stored in
~/.ansible_async/on the remote host
Troubleshooting
Job Timed Out
Increase async value:
# Default might be too short
async: 7200 # 2 hours
Can't Find Job
Job IDs are host-specific. Don't mix up jobs across hosts:
# Each host has its own job
- async_status:
jid: "{{ backup_job.ansible_job_id }}"
# Runs on the same host that started the job
Cleanup Old Jobs
- name: Clean up async job files
ansible.builtin.file:
path: "~/.ansible_async/"
state: absent
Related Articles
Conclusion
Use async + poll for any task that takes more than a few seconds. Fire-and-forget (poll: 0) lets you start multiple long tasks simultaneously and check on them later with async_status. Key pattern: start task → do other work → check status with until: result.finished. Always set async higher than the expected runtime. This is essential for backups, builds, large downloads, and OS updates.