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

ParameterDescription
asyncMaximum runtime in seconds
pollCheck interval (0 = fire-and-forget)
async_statusModule to check job status
jidJob 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 loop on the async_status check — use loop on the original task
  • Task must complete within async seconds 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

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.