When automating server provisioning with Ansible, handlers are typically used for idempotent operations like service restarts. By design, handlers only execute when notified by a task that reports changed=true
. But what if you need to ensure a handler runs every time your playbook executes?
The cleanest solution is using Ansible's meta
module to flush handlers:
tasks:
- name: Force handler execution
meta: flush_handlers
- name: Explicitly trigger nginx restart
meta: flush_handlers
notify: nginx-restart
Another common pattern is creating a task that always reports changed:
tasks:
- name: Force nginx restart by creating dummy change
command: /bin/true
changed_when: true
notify: nginx-restart
For service management specifically, you might bypass handlers entirely:
tasks:
- name: Always restart nginx
service:
name: nginx
state: restarted
The meta
approach is best when you need handler ordering preserved. The dummy task method works well when you want to conditionally force handlers. Direct service commands are simplest but lose handler benefits.
tasks:
- name: Conditionally force handlers
command: /bin/true
changed_when: force_handlers | default(false)
notify:
- nginx-restart
- postfix-restart
Remember that handlers:
- Run once per playbook run even if notified multiple times
- Execute in the order listed in handlers section
- Only run at the end of each play unless flushed
When working with Ansible handlers, you'll quickly discover they only trigger under specific conditions - typically when a notify
directive is called from a changed task. This becomes problematic when you need guaranteed service starts or restarts during playbook execution.
1. Using the meta Module
The cleanest approach uses Ansible's meta
module to explicitly flush handlers:
tasks:
- name: Force nginx restart
meta: flush_handlers
- name: Explicitly call handler
meta: flush_handlers
when: true # Always executes
2. Creating a Dummy Changed Task
You can trick Ansible into thinking a change occurred:
tasks:
- name: Force handler execution
command: /bin/true
changed_when: true
notify: nginx-restart
3. Direct Service Module Execution
For critical services, sometimes bypassing handlers entirely makes sense:
tasks:
- name: Ensure nginx is running
ansible.builtin.service:
name: nginx
state: restarted
enabled: yes
Method | Use Case | Idempotent |
---|---|---|
meta flush_handlers | Cleanest solution | ✅ |
Dummy changed task | Compatibility with older Ansible | ✅ |
Direct service call | Mission-critical services | ✅ |
Combine forced execution with conditionals for more control:
tasks:
- name: Check if handler should run
stat:
path: /tmp/force_restart
register: force_file
- name: Force handler if file exists
meta: flush_handlers
when: force_file.stat.exists
- Handler execution order isn't guaranteed across multiple flushes
- Excessive forced executions may impact performance
- Debugging becomes harder when handlers run unexpectedly