Introduction

Time management is essential in infrastructure automation. Whether you need to wait for a service to start, pause between deployments, or delay execution until a port is open, Ansible provides several mechanisms for controlling timing in your playbooks.

This guide covers the three main approaches: ansible.builtin.wait_for (condition-based waiting), ansible.builtin.pause (simple delays), and async tasks with polling.

The wait_for Module

The ansible.builtin.wait_for module waits for a condition to be met before continuing. It can wait for:

  • A timeout (simple sleep)
  • A port to become available
  • A file to exist or contain specific content
  • A connection to be established

Parameters

ParameterTypeDefaultDescription
timeoutint300Maximum seconds to wait
delayint0Seconds to wait before starting checks
portint—TCP port to poll
hoststring127.0.0.1Host to check
pathstring—File path to check for
search_regexstring—Regex to match in file or port output
statestringstartedstarted, stopped, present, absent, drained
sleepint1Seconds between checks
connect_timeoutint5Timeout for each connection attempt
msgstring—Custom failure message

Basic Examples

Simple Sleep (Wait N Seconds)

---
- name: Wait 30 seconds
  hosts: all
  tasks:
    - name: Sleep for 30 seconds
      ansible.builtin.wait_for:
        timeout: 30
      delegate_to: localhost

    - name: Display message after wait
      ansible.builtin.debug:
        msg: "Waited 30 seconds - continuing"

Note: When using wait_for as a simple sleep, always delegate_to: localhost to avoid waiting on each host sequentially.

Wait for a Port to Open

---
- name: Wait for service startup
  hosts: webservers
  tasks:
    - name: Start nginx
      ansible.builtin.systemd:
        name: nginx
        state: started

    - name: Wait for port 80 to be available
      ansible.builtin.wait_for:
        port: 80
        delay: 2
        timeout: 30
        msg: "Nginx failed to start within 30 seconds"

Wait for a File to Exist

---
- name: Wait for application readiness
  hosts: appservers
  tasks:
    - name: Start application
      ansible.builtin.systemd:
        name: myapp
        state: started

    - name: Wait for PID file
      ansible.builtin.wait_for:
        path: /var/run/myapp.pid
        timeout: 60

    - name: Wait for ready marker
      ansible.builtin.wait_for:
        path: /var/log/myapp/startup.log
        search_regex: "Application started successfully"
        timeout: 120

Wait for Port to Close (Service Stop)

---
- name: Graceful shutdown
  hosts: all
  tasks:
    - name: Stop service
      ansible.builtin.systemd:
        name: myapp
        state: stopped

    - name: Wait for port 8080 to close
      ansible.builtin.wait_for:
        port: 8080
        state: stopped
        timeout: 60
        msg: "Service did not stop within 60 seconds"

Advanced Patterns

Wait for Remote Host Reboot

---
- name: Reboot and wait for return
  hosts: all
  tasks:
    - name: Reboot the server
      ansible.builtin.reboot:
        reboot_timeout: 300
        msg: "Ansible initiated reboot"

    # Alternative manual approach:
    - name: Wait for SSH to come back
      ansible.builtin.wait_for:
        host: "{{ ansible_host }}"
        port: 22
        delay: 30
        timeout: 300
        state: started
      delegate_to: localhost
      when: false  # disabled - reboot module handles this

Wait for Database Readiness

---
- name: Database initialization
  hosts: dbservers
  tasks:
    - name: Start PostgreSQL
      ansible.builtin.systemd:
        name: postgresql
        state: started

    - name: Wait for PostgreSQL port
      ansible.builtin.wait_for:
        port: 5432
        delay: 5
        timeout: 60

    - name: Wait for PostgreSQL to accept connections
      ansible.builtin.command: pg_isready -h localhost -p 5432
      register: pg_ready
      retries: 10
      delay: 3
      until: pg_ready.rc == 0
      changed_when: false

Rolling Deployment with Delays

---
- name: Rolling deployment
  hosts: webservers
  serial: 1
  tasks:
    - name: Remove from load balancer
      ansible.builtin.uri:
        url: "http://lb.example.com/api/deregister/{{ inventory_hostname }}"
        method: POST

    - name: Wait for connections to drain
      ansible.builtin.wait_for:
        port: 8080
        state: drained
        timeout: 60

    - name: Deploy new version
      ansible.builtin.copy:
        src: app-v2.jar
        dest: /opt/app/app.jar
      notify: Restart app

    - name: Flush handlers
      ansible.builtin.meta: flush_handlers

    - name: Wait for new version to be ready
      ansible.builtin.wait_for:
        port: 8080
        delay: 5
        timeout: 60

    - name: Health check
      ansible.builtin.uri:
        url: "http://localhost:8080/health"
        status_code: 200
      retries: 5
      delay: 3
      register: health
      until: health.status == 200

    - name: Re-register with load balancer
      ansible.builtin.uri:
        url: "http://lb.example.com/api/register/{{ inventory_hostname }}"
        method: POST

  handlers:
    - name: Restart app
      ansible.builtin.systemd:
        name: myapp
        state: restarted

Wait with Custom Interval

- name: Wait for slow service (check every 5 seconds)
  ansible.builtin.wait_for:
    port: 9200
    delay: 10
    timeout: 300
    sleep: 5
    msg: "Elasticsearch failed to start"

The pause Module (Simple Delays)

For simple interactive or timed pauses without condition checking:

---
- name: Deployment with confirmation
  hosts: all
  tasks:
    - name: Pause for manual verification
      ansible.builtin.pause:
        prompt: "Verify the deployment looks correct. Press Enter to continue or Ctrl+C to abort"

    - name: Pause for 10 seconds
      ansible.builtin.pause:
        seconds: 10

    - name: Pause for 2 minutes
      ansible.builtin.pause:
        minutes: 2

wait_for vs pause

Featurewait_forpause
Condition-based✅ Yes❌ No
Port checking✅ Yes❌ No
File watching✅ Yes❌ No
Simple delay✅ (with timeout)✅ Yes
Interactive prompt❌ No✅ Yes
Runs onTarget (or delegated)Controller

Async Tasks with Polling

For long-running tasks where you don't want to block:

---
- name: Long-running operations
  hosts: all
  tasks:
    - name: Start database migration (async)
      ansible.builtin.command: /opt/app/migrate.sh
      async: 600  # Allow up to 10 minutes
      poll: 0     # Don't wait, fire and forget
      register: migration_job

    - name: Do other tasks while migration runs
      ansible.builtin.debug:
        msg: "Migration running in background..."

    - 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

Complete Example: Service Deployment with Health Checks

---
- name: Deploy and verify service
  hosts: appservers
  become: true
  vars:
    app_port: 8080
    health_endpoint: "/actuator/health"
    startup_timeout: 120
  tasks:
    - name: Deploy application artifact
      ansible.builtin.copy:
        src: "builds/app-{{ app_version }}.jar"
        dest: /opt/app/app.jar
        owner: appuser
        group: appuser
        mode: '0644'
      notify: Restart application

    - name: Flush handlers to trigger restart
      ansible.builtin.meta: flush_handlers

    - name: Wait for application port
      ansible.builtin.wait_for:
        port: "{{ app_port }}"
        delay: 10
        timeout: "{{ startup_timeout }}"
        msg: "Application failed to bind to port {{ app_port }} within {{ startup_timeout }}s"

    - name: Wait for health endpoint
      ansible.builtin.uri:
        url: "http://localhost:{{ app_port }}{{ health_endpoint }}"
        status_code: 200
      register: health_check
      retries: 10
      delay: 5
      until: health_check.status == 200

    - name: Report deployment success
      ansible.builtin.debug:
        msg: "Application deployed and healthy on {{ inventory_hostname }}:{{ app_port }}"

  handlers:
    - name: Restart application
      ansible.builtin.systemd:
        name: myapp
        state: restarted

Troubleshooting

wait_for Times Out

Timeout when waiting for 127.0.0.1:8080

Check:

  1. Is the service actually starting? Check systemctl status and logs
  2. Is it binding to the correct interface? (0.0.0.0 vs 127.0.0.1)
  3. Is a firewall blocking the port?
  4. Increase timeout if the service is legitimately slow

wait_for with delegate_to Not Working

# Wrong - checks port on controller, not target
- ansible.builtin.wait_for:
    port: 80
  delegate_to: localhost

# Right - specify the target host explicitly
- ansible.builtin.wait_for:
    host: "{{ ansible_host }}"
    port: 80
  delegate_to: localhost

Conclusion

Time management in Ansible revolves around three core tools: wait_for for condition-based waiting (ports, files, patterns), pause for simple delays and interactive prompts, and async/poll for background tasks. The key to robust automation is combining these with retries, until, and proper timeout values to handle real-world timing variability gracefully.