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
| Parameter | Type | Default | Description |
|---|---|---|---|
timeout | int | 300 | Maximum seconds to wait |
delay | int | 0 | Seconds to wait before starting checks |
port | int | — | TCP port to poll |
host | string | 127.0.0.1 | Host to check |
path | string | — | File path to check for |
search_regex | string | — | Regex to match in file or port output |
state | string | started | started, stopped, present, absent, drained |
sleep | int | 1 | Seconds between checks |
connect_timeout | int | 5 | Timeout for each connection attempt |
msg | string | — | 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
| Feature | wait_for | pause |
|---|---|---|
| Condition-based | ✅ Yes | ❌ No |
| Port checking | ✅ Yes | ❌ No |
| File watching | ✅ Yes | ❌ No |
| Simple delay | ✅ (with timeout) | ✅ Yes |
| Interactive prompt | ❌ No | ✅ Yes |
| Runs on | Target (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:
- Is the service actually starting? Check
systemctl statusand logs - Is it binding to the correct interface? (0.0.0.0 vs 127.0.0.1)
- Is a firewall blocking the port?
- Increase
timeoutif 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
Related Articles
- Ansible assert Module — Validate conditions in playbooks
- Ansible uri Module — HTTP requests for health checks
- Ansible Error Handling — Block/rescue for failures
- Ansible async and poll — Parallel long-running tasks
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.