Ansible Delay and Sleep — Wait Between Tasks

Introduction

Sometimes tasks need breathing room — wait for a service to start, pause between API calls to avoid rate limits, or delay a reboot check. Ansible provides several mechanisms: pause, wait_for, until loops with delay, and async. This guide covers when to use each.

ansible.builtin.pause

# Wait 30 seconds
- name: Wait for service to stabilize
  ansible.builtin.pause:
    seconds: 30

# Wait with a message
- name: Pause before cutover
  ansible.builtin.pause:
    minutes: 2
    prompt: "Waiting 2 minutes for DNS propagation..."

# Wait for user confirmation
- name: Confirm before proceeding
  ansible.builtin.pause:
    prompt: "Press Enter to continue or Ctrl+C to abort"

pause Parameters

ParameterDescription
secondsNumber of seconds to pause
minutesNumber of minutes to pause
promptMessage to display during pause
echoWhether to echo user input (default: true)

wait_for — Wait for a Condition

# Wait for port to be open
- name: Wait for PostgreSQL to start
  ansible.builtin.wait_for:
    port: 5432
    host: localhost
    delay: 2
    timeout: 60

# Wait for file to appear
- name: Wait for lock file to be removed
  ansible.builtin.wait_for:
    path: /tmp/deploy.lock
    state: absent
    timeout: 300

# Wait for string in file
- name: Wait for app to log "ready"
  ansible.builtin.wait_for:
    path: /var/log/myapp.log
    search_regex: "Application started successfully"
    timeout: 120

wait_for Parameters

ParameterDefaultDescription
port—TCP port to check
host127.0.0.1Host to check
path—File path to check
search_regex—Regex to search in file
statestartedstarted, stopped, present, absent, drained
delay0Seconds to wait before first check
timeout300Max seconds to wait
sleep1Seconds between checks

until Loop with delay

# Retry API call until success
- name: Wait for API to return healthy
  ansible.builtin.uri:
    url: http://localhost:8080/health
    status_code: 200
  register: health_check
  until: health_check.status == 200
  retries: 30
  delay: 10  # Wait 10 seconds between retries

# Retry command until output matches
- name: Wait for cluster to be ready
  ansible.builtin.command: kubectl get nodes
  register: nodes_output
  until: "'Ready' in nodes_output.stdout"
  retries: 20
  delay: 15
  changed_when: false

Common Patterns

Reboot and Wait

- name: Reboot the server
  ansible.builtin.reboot:
    reboot_timeout: 600
    pre_reboot_delay: 5
    post_reboot_delay: 30
    connect_timeout: 10

# Or manually:
- name: Reboot
  ansible.builtin.command: shutdown -r now
  async: 1
  poll: 0

- name: Wait for host to come back
  ansible.builtin.wait_for_connection:
    delay: 30
    timeout: 300

Rate-Limited API Calls

- name: Create resources with rate limiting
  ansible.builtin.uri:
    url: "https://api.example.com/resources"
    method: POST
    body_format: json
    body:
      name: "{{ item.name }}"
  loop: "{{ resources }}"
  loop_control:
    pause: 2  # 2 seconds between iterations

Service Start + Verify

- name: Start application
  ansible.builtin.service:
    name: myapp
    state: started

- name: Wait for app to listen on port
  ansible.builtin.wait_for:
    port: 8080
    delay: 5
    timeout: 60

- name: Verify app responds
  ansible.builtin.uri:
    url: http://localhost:8080/health
  register: health
  until: health.status == 200
  retries: 5
  delay: 3

Rolling Deployment Spacing

---
- name: Rolling deploy
  hosts: webservers
  serial: 1  # One host at a time
  tasks:
    - name: Deploy new version
      ansible.builtin.copy:
        src: app.tar.gz
        dest: /opt/app/

    - name: Restart service
      ansible.builtin.service:
        name: webapp
        state: restarted

    - name: Wait for health check
      ansible.builtin.wait_for:
        port: 8080
        delay: 10
        timeout: 60

    - name: Pause between hosts
      ansible.builtin.pause:
        seconds: 30
      when: ansible_play_hosts_all | length > 1

Database Migration Wait

- name: Run migration
  ansible.builtin.command: /opt/app/migrate.sh
  async: 600  # Allow up to 10 minutes
  poll: 0
  register: migration_job

- name: Wait for migration to complete
  ansible.builtin.async_status:
    jid: "{{ migration_job.ansible_job_id }}"
  register: job_result
  until: job_result.finished
  retries: 60
  delay: 10

Comparison

MethodUse CaseBlocks Play?
pauseFixed wait time, user promptYes
wait_forPort/file/string conditionYes (with timeout)
until + delayRetry until successYes (with retries)
loop_control.pauseSpace between loop itemsYes
async + poll: 0Fire and forgetNo
wait_for_connectionSSH/WinRM reconnectYes

Troubleshooting

IssueSolution
pause not working in --check modeExpected — pause is skipped in check mode
wait_for times outIncrease timeout, check service actually starts
until loop exhausts retriesIncrease retries or delay, check the condition
Task hangs foreverSet explicit timeout on all wait operations
pause slows down large inventoriesUse run_once: true if pause should happen once

Best Practices

  1. Prefer wait_for over pause — condition-based waits are more reliable
  2. Always set timeout — never wait forever
  3. Use until for external services — APIs, databases, clusters
  4. Use loop_control.pause for rate limits — cleaner than pause task
  5. Use async for long operations — don't block the playbook
  6. Add delay to wait_for — give services time to begin starting

Conclusion

Use pause for fixed delays and user prompts. Use wait_for when waiting for ports, files, or strings. Use until loops with delay for retry-based waiting (API health checks, cluster readiness). Use loop_control.pause to space out loop iterations. Always set timeouts to prevent infinite waits.