Handlers run only at the end of a play, not immediately after the task that notifies them.
Use notify on a task to trigger one or more handlers by name.
Use listen on a handler to allow multiple tasks to notify it via a common topic.
Handlers are skipped if a task fails in the play (unless ignore_errors: yes or force_handlers: true is set).
Use meta: flush_handlers to force all pending handlers to run immediately, e.g., before a task that depends on the change.
Handler ordering is the order they are defined in the handlers block, not the order of notifications.
Idempotency: handlers only run if the notifying task reports 'changed' status.
Use changed_when: false on tasks that should never trigger handlers (e.g., checks).
✦ Definition~90s read
What is Ansible Handlers and Notifications?
Ansible handlers are special tasks that run only when notified by a task that reports a 'changed' status. They are defined in a handlers block (or included via import_tasks) and triggered via the notify keyword. The key design principle: handlers run once, at the end of the play, in the order they are defined—not the order they are notified.
★
Think of Ansible handlers as the 'cleanup crew' at a party.
This prevents multiple restarts if the same handler is notified by multiple tasks.
Handlers solve the 'restart only on change' problem elegantly. Without handlers, you'd either restart services unconditionally (wasteful and causes downtime) or rely on complex conditionals. Handlers make playbooks both efficient and idempotent: if a task doesn't change anything, the handler doesn't run.
In the Ansible ecosystem, handlers are a core pattern for service management, cache invalidation, and any action that should only occur when a configuration change is made. They integrate with listen to allow multiple tasks to trigger the same handler via a common topic, and flush_handlers to force immediate execution when ordering matters.
Plain-English First
Think of Ansible handlers as the 'cleanup crew' at a party. The tasks are the guests who make messes (change files, restart services). When a guest makes a mess, they send a notification (notify) to the cleanup crew. But the crew doesn't act immediately—they wait until the party ends (end of the play). This is efficient because if multiple guests make a mess, the crew only cleans once. However, there's a trap: if a guest gets into a fight (task fails) before the party ends, the cleanup crew might never show up! In production, this means a configuration change might trigger a restart, but if a later task fails, the restart never happens, leaving the system in a half-baked state. That's where flush_handlers comes in—it's like telling the crew to clean up right now, before the next guest arrives.
I'll never forget the 3 AM outage. We had an Ansible playbook that deployed a new version of our API service. It updated the config file, notified a handler to restart nginx, then ran a database migration. The migration failed—and nginx never restarted. The old config was still active, but the database schema had changed. Users got 500 errors for 45 minutes while we scrambled. The root cause? The handler never ran because the play failed before the playbook ended. That's when I truly understood Ansible handlers—and their dangerous default behavior.
Handlers were introduced in Ansible 1.0 to solve a common problem: how to restart a service only when its configuration actually changes. Instead of always restarting (which causes downtime), you notify a handler and it runs once at the end. It's elegant, but the 'run at end' behavior has burned many teams.
In this article, I'll cover everything you need to know about handlers: the notify keyword, the handlers block, the listen keyword for grouping, meta: flush_handlers for immediate execution, handler ordering, the infamous trap of handlers not running on failure, and idempotency best practices. Every example is production-tested.
The notify Keyword: How to Trigger a Handler
The notify keyword is used on a task to indicate that one or more handlers should be triggered if the task reports a 'changed' status. The value can be a single handler name or a list of handler names.
If the template changes the file, the 'restart nginx' handler is added to the notification queue. If the file is already up-to-date, the task reports 'ok' and the handler is not notified.
Gotcha: If the task uses changed_when to override the changed status, handlers only run if you explicitly set changed_when: true. For example: ``yaml - name: Check if reboot required ansible.builtin.stat: path: /var/run/reboot-required register: reboot_file changed_when: reboot_file.stat.exists notify: reboot server ``
In production, I've seen teams accidentally set changed_when: false on tasks that should trigger handlers, completely breaking the notification chain.
If you have two handlers with the same name (e.g., from included files), Ansible will silently use only the last one defined. Use --syntax-check to catch duplicates.
📊 Production Insight
We once had a handler named 'restart nginx' defined in two different included role files. The second definition overwrote the first, causing the wrong service to restart. We caught it by running ansible-playbook --syntax-check which flagged duplicate handler names.
🎯 Key Takeaway
Always use unique handler names across your entire playbook and validate with --syntax-check.
thecodeforge.io
Ansible Handlers Notifications
The handlers Block: Defining the Actions
Handlers are defined in a handlers block at the play level, or in a role's handlers/main.yml. Each handler is essentially a task with a name and a module action.
Handlers can use any module, but common ones are service, systemd, command, or uri. They can also include variables and conditionals.
Order matters: Handlers run in the order they are defined in the handlers block, not the order they are notified. For example: ``yaml handlers: - name: restart app ansible.builtin.service: name: app state: restarted - name: restart nginx ansible.builtin.service: name: nginx state: restarted `` Even if 'restart nginx' is notified first, 'restart app' will run first because it's defined first.
Gotcha: If you use include_tasks or import_tasks for handlers, the order is determined by the order of inclusion. This can be surprising if you include multiple handler files.
Always define handlers in the order you want them to execute, not the order you think they'll be notified.
📊 Production Insight
In a microservices deployment, we had handlers for 'restart service A' and 'restart service B'. Service B depended on A, but we defined B first. Handlers ran B before A, causing a brief outage. We fixed it by reordering the handlers block.
🎯 Key Takeaway
Define handlers in dependency order (dependencies first) to avoid ordering surprises.
The listen Keyword: Grouping Multiple Triggers
The listen keyword allows a handler to be triggered by multiple tasks via a common topic. Instead of notifying a handler by name, tasks notify a 'listen' topic, and any handler that listens to that topic runs.
Tasks can notify the topic: ``yaml - name: Update nginx config template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf notify: "restart web services" ``
This is useful when multiple changes require the same set of actions. It also decouples the task from the exact handler name.
Gotcha: The listen value must be a string. If you use a variable, ensure it resolves to a string. Also, if two handlers listen to the same topic, they both run (in definition order).
site.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
- name: Use listen to group triggers
hosts: web
handlers:
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
listen: "nginx config update"
tasks:
- name: Update nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: "nginx config update"
💡Use listen for service groups
If you have multiple services that all need restarting on any config change, define a single listen topic and have each service's handler listen to it.
📊 Production Insight
We had a listen topic 'reload proxy' that triggered both nginx and haproxy reloads. One day, a junior engineer added a new handler with the same listen topic but for a different service, causing unintended reloads. We now enforce naming conventions for listen topics.
🎯 Key Takeaway
Use listen to group related handlers, but be careful with naming collisions—treat listen topics like a shared namespace.
thecodeforge.io
Ansible Handlers Notifications
meta: flush_handlers for Immediate Execution
By default, handlers run at the end of the play. But sometimes you need a handler to run immediately before a subsequent task. The meta: flush_handlers task forces all pending handlers to run right away.
Without flush_handlers, the restart would happen after the play ends, and the health check would fail because the old process is still running.
Gotcha: flush_handlers only flushes handlers that have been notified up to that point. If a later task notifies a handler, it won't run until the next flush or end of play.
Production pattern: Use flush_handlers before any task that depends on the change being active, like health checks, smoke tests, or database migrations.
site.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
- name: Flush handlers immediately
hosts: web
tasks:
- name: Update nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: Flush handlers now
ansible.builtin.meta: flush_handlers
- name: Next task after restart
ansible.builtin.debug:
msg: "Nginx was restarted"
⚠ flush_handlers does not prevent handler execution on failure
If a task after flush_handlers fails, handlers that were flushed have already run. But handlers notified after the failure are still skipped unless force_handlers: true is set.
📊 Production Insight
In a zero-downtime deployment, we used flush_handlers to restart the app before running a database migration that required the new schema. Without it, the migration would fail against the old version.
🎯 Key Takeaway
Use meta: flush_handlers whenever you need a handler's effect to be visible before the next task.
Handler Ordering: Why Definition Order Matters
Handlers execute in the order they are defined in the handlers block, not the order they are notified. This is a common source of confusion.
Even if 'restart app' is notified first, 'restart database' runs first because it's defined first.
Why this matters: If your app depends on the database, you want the database handler to run first. So define it first.
Gotcha: When using include_handlers or import_tasks for handlers, the order is determined by the order of inclusion. This can be tricky if you include multiple files from different roles.
Best practice: Define all handlers in a single file with explicit ordering, or use listen topics to group them logically.
site.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
- name: Handler ordering matters
hosts: web
handlers:
- name: first handler
ansible.builtin.debug:
msg: "First"
- name: second handler
ansible.builtin.debug:
msg: "Second"
tasks:
- name: Trigger both
ansible.builtin.debug:
msg: "Trigger"
notify:
- first handler
- second handler
🔥Handler ordering is not influenced by notify order
Always check the definition order of your handlers. Use ansible-playbook --list-handlers to see the order.
📊 Production Insight
We once had a handler that removed a temporary file, defined after a handler that created it. The removal ran first (because it was defined first) and the creation never happened. We reordered the handlers to fix it.
🎯 Key Takeaway
Definition order = execution order. Organize handlers by dependency, not by notification sequence.
The Common Trap: Handlers Not Running When Tasks Fail
This is the most dangerous handler behavior: if any task in the play fails (and ignore_errors is not set), all pending handlers are skipped at the end of the play. This is by design to avoid applying changes from an incomplete run.
If the migration fails, the 'restart app' handler never runs. The config file is updated, but the app is still running the old version. This can lead to inconsistent state.
Solutions: 1. Use force_handlers: true at the play level to force handlers to run even on failure. 2. Use meta: flush_handlers before the risky task to ensure the handler runs before the potential failure. 3. Use ignore_errors: yes on tasks that shouldn't block handlers (but be careful).
site.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
---
- name: Handlers not run on task failure
hosts: web
tasks:
- name: Update config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: This task fails
ansible.builtin.command: /bin/false
ignore_errors: yes # if not ignored, handlers won't run
⚠ Handlers are skipped on ANY task failure in the play
Even if the failed task is unrelated to the handler, all pending handlers are discarded. Use force_handlers: true to override.
📊 Production Insight
The 3 AM outage I mentioned earlier was exactly this: a migration failed, handlers were skipped, nginx never restarted. We now always set force_handlers: true on any play that modifies critical services.
🎯 Key Takeaway
Always set force_handlers: true on plays that manage services to ensure handlers run even on failure.
Idempotency and Handlers: Making Sure They Only Run When Needed
Handlers are inherently idempotent because they only run when the notifying task reports 'changed'. But you can break idempotency if your tasks always report 'changed'.
Common mistakes
Using command or shell modules without creates, removes, or changed_when.
Using copy with force: yes but the source file changes every time (e.g., from a build artifact).
Using template with a source that changes every run (e.g., includes a timestamp).
Best practices
Use changed_when to explicitly define when a task should be considered changed.
For command tasks, use creates or removes to avoid running unnecessarily.
Use register and conditionals to only notify handlers when actual changes occur.
Example of a properly idempotent task: ``yaml - name: Download new artifact get_url: url: "{{ artifact_url }}" dest: /opt/app/app.jar checksum: "sha256:{{ artifact_checksum }}" register: download notify: restart app ``
If the checksum matches, the file is not downloaded, and the handler is not notified.
site.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
- name: Idempotent handler
hosts: web
handlers:
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
tasks:
- name: Ensure config is correct
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
# Handler only runs if template changed
💡Use checksums to ensure idempotency
For file downloads, always provide a checksum to avoid unnecessary downloads and handler triggers.
📊 Production Insight
We had a playbook that deployed a new version every hour. The artifact URL always pointed to the latest build, so the file was always 'changed' even if the content was identical. We added checksum validation, and handler notifications dropped by 90%.
🎯 Key Takeaway
Idempotent tasks prevent unnecessary handler runs. Use changed_when, creates, and checksums to ensure tasks only report 'changed' when something actually changes.
Using force_handlers to Guarantee Handler Execution
The force_handlers play-level directive ensures that all notified handlers run even if a task fails. This is critical for plays that must apply changes regardless of subsequent failures.
With force_handlers: true, even if the migration fails, the 'restart app' handler will run. This prevents the app from running with an old config.
Caveat: force_handlers only ensures handlers run. It does not prevent the play from failing—Ansible will still report a failure. But the handler's effect is applied.
When to use: Always use on plays that manage production services. Reserve the default behavior for plays where you want to roll back on failure (e.g., database schema changes).
site.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
---
- name: Force handlers even on failure
force_handlers: yes
hosts: web
tasks:
- name: Update config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: This task fails
ansible.builtin.command: /bin/false
🔥force_handlers is a play-level keyword
It cannot be set per task or per handler. It applies to the entire play.
📊 Production Insight
We now have a policy: any play that restarts a service must have force_handlers: true. This single change prevented multiple incidents.
🎯 Key Takeaway
Use force_handlers: true on any play where handler execution is critical, especially service restarts.
Handlers with include_tasks and import_tasks
Handlers can be defined in separate files and included using include_tasks or import_tasks. This is common in roles.
Gotcha: When using include_tasks (dynamic inclusion), handler names are evaluated at runtime. This can cause issues if the same handler name is defined in multiple included files—the last one wins.
Best practice: Use import_tasks (static) for handlers to ensure proper ordering and uniqueness checking at parse time. Dynamic inclusion with include_tasks can lead to duplicate handler names being silently overwritten.
Production pattern: In roles, always define handlers in handlers/main.yml and use import_tasks in the playbook if you need to include role handlers in a specific order.
site.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
---
- name: Handlers with include_tasks
hosts: web
tasks:
- name: Include tasks
ansible.builtin.include_tasks:
file: update_config.yml
handlers:
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
⚠ Dynamic include_tasks can cause duplicate handler names
If you include the same handler file twice, the second inclusion overwrites the first. Use import_tasks for handlers when possible.
📊 Production Insight
We had a role that was included twice in a playbook (once for each service instance). The handlers from the second inclusion overwrote the first, causing the first service to never restart. We refactored to use import_tasks and unique handler names.
🎯 Key Takeaway
Prefer import_tasks for handlers to avoid runtime surprises with duplicate names.
Debugging Handlers: Verbose Output and List Commands
When handlers misbehave, use these commands to debug:
List all handlers in a playbook:
```bash
ansible-playbook playbook.yml --list-handlers
```
Run with verbose output to see handler notifications:
Gotcha: In Ansible 2.x, handlers that are skipped due to a failed task do not appear in the output at all. You only see the failure. This is why force_handlers: true is important.
playbook.ymlBASH
1
2
3
4
5
# Run playbook with verbose output to see handler execution
ansible-playbook -i inventory.ini site.yml -v
# List handlers in a playbook
ansible-playbook -i inventory.ini site.yml --list-handlers
💡Use --list-handlers to preview execution order
Run this before any production deployment to verify handler ordering.
📊 Production Insight
During a major outage, we ran ansible-playbook --list-handlers and discovered that a handler we thought was defined was actually missing due to a typo in the file name. The command saved us hours of debugging.
🎯 Key Takeaway
Always validate handler configuration with --list-handlers and -v before running on production.
Advanced Patterns: Conditional Handlers and Loops
Handlers can include conditionals (when) and can be used with loops, but there are nuances.
Gotcha: If you use listen with a loop, each item in the loop is a separate handler instance. Notifying the listen topic triggers all of them.
Production pattern: Use listen with a loop to restart multiple services from a single notification. For example: ``yaml handlers: - name: restart web stack listen: "restart web" service: name: "{{ item }}" state: restarted loop: - nginx - php-fpm ``
Then any task can notify 'restart web' and both services restart.
The loop is executed once per handler notification. If the handler is notified multiple times, the loop runs each time (but only once due to handler deduplication).
📊 Production Insight
We used a loop in a handler to restart a microservice cluster. When a config changed, all 10 instances restarted. But because handlers deduplicate, it only happened once per play. This pattern saved us from writing 10 separate handlers.
🎯 Key Takeaway
Use loops in handlers to manage multiple services with a single handler definition, but be aware of the deduplication behavior.
thecodeforge.io
Ansible Handlers Notifications
Common Pitfalls and How to Avoid Them
Here are the most frequent mistakes I've seen with handlers:
Assuming handlers run immediately: They don't. Use flush_handlers if you need immediate effect.
Not using force_handlers: Leads to skipped restarts on failure.
Duplicate handler names: Silently overwritten. Use unique names or import_tasks to catch duplicates.
Wrong ordering: Define handlers in dependency order.
Not checking idempotency: Tasks that always report 'changed' cause unnecessary handler runs.
Using listen with variables that might be undefined: Always ensure the listen topic is a literal string or a variable that resolves.
Forgetting that handlers are skipped on unreachable hosts: If a host becomes unreachable during the play, handlers for that host are skipped.
Production checklists
Always add force_handlers: true to plays with service restarts.
---
- name: Avoid common pitfalls
hosts: web
handlers:
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
tasks:
- name: Update config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: Flush handlers before next critical task
ansible.builtin.meta: flush_handlers
- name: Next task depends on restart
ansible.builtin.debug:
msg: "Nginx restarted"
⚠ Handlers are skipped on unreachable hosts
If a host becomes unreachable during the play (e.g., network issue), handlers for that host are not executed. This is another reason to use force_handlers: true and handle unreachability separately.
📊 Production Insight
We had a playbook that restarted a load balancer after config change. The load balancer host became unreachable mid-play due to a network blip. The handler never ran, and the config was applied but not active. We added a retry mechanism and force_handlers: true.
🎯 Key Takeaway
Plan for handler failures due to host unreachability and use force_handlers to mitigate.
● Production incidentPOST-MORTEMseverity: high
The Handler That Never Ran
Symptom
Users received 500 errors after a deployment. Nginx was still running with old config despite config file being updated.
Assumption
The nginx restart handler must have run because the config file task reported 'changed'.
Root cause
The handler was notified, but because a subsequent task (database migration) failed, Ansible skipped all pending handlers at the end of the play.
Fix
Added force_handlers: true to the play and used meta: flush_handlers before the critical migration task.
Key lesson
Always assume handlers might not run if any task fails.
Use force_handlers: true or flush_handlers to ensure critical actions like restarts happen regardless.
Production debug guideSymptom → Root cause → Fix4 entries
Symptom · 01
Handler never runs despite task reporting 'changed'
→
Fix
Check if any later task failed. Add force_handlers: true to the play. Use -v to see handler execution status.
Symptom · 02
Handler runs multiple times or not at all when using listen
→
Fix
Ensure handler names are unique. listen topics can overlap; use ansible-playbook --syntax-check to detect duplicates.
Symptom · 03
Handler runs before a dependent task
→
Fix
Handlers run only at end of play by default. Insert meta: flush_handlers before the dependent task.
Symptom · 04
Handler runs in wrong order (e.g., restart before config write)
→
Fix
Check handler definition order. Handlers execute in definition order, not notification order. Reorder if needed.
★ Ansible Handlers and Notifications Quick Referenceprint this for your desk
Ensure listen value is a string, not a variable that might not resolve
Handler runs but no change needed+
Immediate action
Check idempotency
Commands
ansible-playbook playbook.yml --diff
Fix now
Add changed_when: false to tasks that should never trigger handlers
Handler Execution: Default vs force_handlers vs flush_handlers
Scenario
Default Behavior
force_handlers: true
flush_handlers before failure
No task failure
Handlers run at end of play
Handlers run at end of play
Handlers run immediately, then remaining tasks continue
Task fails before handler notification
No handlers run (none pending)
No handlers run (none pending)
No handlers run (none pending)
Task fails after handler notification
All pending handlers skipped
All pending handlers run at end
Flushed handlers run; handlers notified after failure are skipped
Host unreachable mid-play
Handlers for that host skipped
Handlers for that host skipped (force_handlers does not override unreachability)
Same as default
Multiple handlers notified
Run in definition order
Same
Same (but only flushed ones)
⚙ Quick Reference
12 commands from this guide
File
Command / Code
Purpose
site.yml
- name: Trigger handler on config change
The notify Keyword
site.yml
- name: Define handlers
The handlers Block
site.yml
- name: Use listen to group triggers
The listen Keyword
site.yml
- name: Flush handlers immediately
meta
site.yml
- name: Handler ordering matters
Handler Ordering
site.yml
- name: Handlers not run on task failure
The Common Trap
site.yml
- name: Idempotent handler
Idempotency and Handlers
site.yml
- name: Force handlers even on failure
Using force_handlers to Guarantee Handler Execution
site.yml
- name: Handlers with include_tasks
Handlers with include_tasks and import_tasks
playbook.yml
ansible-playbook -i inventory.ini site.yml -v
Debugging Handlers
site.yml
- name: Conditional handler with loop
Advanced Patterns
site.yml
- name: Avoid common pitfalls
Common Pitfalls and How to Avoid Them
Key takeaways
1
Handlers run only at the end of the play by default; use `meta
flush_handlers` for immediate execution.
2
Always set `force_handlers
true` on plays that manage services to prevent skipped restarts on failure.
3
Handler execution order is determined by definition order, not notification order.
4
Use listen to group multiple handlers under a common topic for decoupled notifications.
5
Duplicate handler names cause silent overwrites; validate with --syntax-check.
6
Ensure tasks are idempotent with changed_when to avoid unnecessary handler triggers.
7
Use ansible-playbook --list-handlers to preview handler order before deployment.
8
Handlers are skipped on unreachable hosts; plan accordingly with retries or force_handlers.
Common mistakes to avoid
6 patterns
×
Not using force_handlers on critical plays
Symptom
Service restart skipped after a failed task, leading to inconsistent state
Fix
Add force_handlers: true to the play
×
Duplicate handler names in included files
Symptom
Only the last defined handler runs
Fix
Use unique names or import_tasks and verify with --syntax-check
×
Assuming handlers run immediately after notify
Symptom
Subsequent tasks see old state (e.g., old service running)
Fix
Insert meta: flush_handlers before dependent tasks
×
Using command/shell without changed_when
Symptom
Handler always runs even if no real change
Fix
Add changed_when: false or use creates/removes
×
Defining handlers in wrong order
Symptom
Service B restarts before Service A, causing dependency failures
Fix
Reorder handlers in definition to match dependency order
×
Using listen with a variable that might be undefined
Symptom
Handler never triggered because listen topic resolved to empty string
Fix
Use literal strings for listen topics or ensure variable is defined
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is the default behavior of Ansible handlers when a task fails?
Q02SENIOR
How can you force handlers to run immediately before a specific task?
Q03SENIOR
What is the difference between `notify` and `listen` in handlers?
Q04SENIOR
Explain how handler ordering is determined in Ansible.
Q05SENIOR
What happens if two handlers have the same name in a playbook?
Q06SENIOR
How can you ensure idempotency when using handlers with command modules?
Q07SENIOR
What is the purpose of `force_handlers` and when should you use it?
Q08SENIOR
Can handlers be used with loops? If so, what is the behavior?
Q01 of 08JUNIOR
What is the default behavior of Ansible handlers when a task fails?
ANSWER
By default, if any task in the play fails, all pending handlers are skipped and not executed. This is to prevent applying partial changes. You can override this with force_handlers: true at the play level.
Q02 of 08SENIOR
How can you force handlers to run immediately before a specific task?
ANSWER
Use the meta: flush_handlers task. This causes all pending handlers to run at that point in the play. It's useful when a subsequent task depends on the handler's effect, like a health check after a service restart.
Q03 of 08SENIOR
What is the difference between `notify` and `listen` in handlers?
ANSWER
notify is used on a task to trigger a handler by its name. listen is a keyword on a handler that allows it to be triggered by a topic name. Multiple tasks can notify the same listen topic, and multiple handlers can listen to the same topic, enabling decoupling and grouping.
Q04 of 08SENIOR
Explain how handler ordering is determined in Ansible.
ANSWER
Handlers run in the order they are defined in the handlers block, not the order they are notified. This means if you have handlers 'restart db' and 'restart app' defined in that order, 'restart db' will always run first regardless of which was notified first.
Q05 of 08SENIOR
What happens if two handlers have the same name in a playbook?
ANSWER
Ansible will silently use only the last handler definition with that name. This can happen when including handler files dynamically. To avoid this, use unique names and run ansible-playbook --syntax-check to detect duplicates.
Q06 of 08SENIOR
How can you ensure idempotency when using handlers with command modules?
ANSWER
Use changed_when to explicitly define when the task should report 'changed', or use creates/removes to avoid running if the result already exists. This prevents the handler from being notified unnecessarily.
Q07 of 08SENIOR
What is the purpose of `force_handlers` and when should you use it?
ANSWER
force_handlers: true ensures that all notified handlers run even if a task fails in the play. It should be used on any play that manages critical services to avoid leaving the system in an inconsistent state after a failure.
Q08 of 08SENIOR
Can handlers be used with loops? If so, what is the behavior?
ANSWER
Yes, handlers can use loops. If a handler has a loop (e.g., restart multiple services), all items in the loop are executed when the handler is triggered. Due to handler deduplication, the loop runs only once per play even if notified multiple times.
01
What is the default behavior of Ansible handlers when a task fails?
JUNIOR
02
How can you force handlers to run immediately before a specific task?
SENIOR
03
What is the difference between `notify` and `listen` in handlers?
SENIOR
04
Explain how handler ordering is determined in Ansible.
SENIOR
05
What happens if two handlers have the same name in a playbook?
SENIOR
06
How can you ensure idempotency when using handlers with command modules?
SENIOR
07
What is the purpose of `force_handlers` and when should you use it?
SENIOR
08
Can handlers be used with loops? If so, what is the behavior?
SENIOR
FAQ · 8 QUESTIONS
Frequently Asked Questions
01
Do handlers run if a task fails with ignore_errors: yes?
Yes, if a task has `ignore_errors: yes`, it does not count as a failure, so handlers will still run at the end of the play.
Was this helpful?
02
Can I have multiple handlers with the same listen topic?
Yes, multiple handlers can listen to the same topic. All of them will run (in definition order) when a task notifies that topic.
Was this helpful?
03
Does flush_handlers run handlers that were notified after the flush?
No, flush_handlers only runs handlers that have been notified up to that point. Handlers notified after the flush will run at the end of the play or at the next flush.
Was this helpful?
04
What is the difference between handlers and tasks with when conditions?
Handlers are triggered only by notifications from tasks that report 'changed'. Tasks with when run based on conditions evaluated at runtime. Handlers are also deduplicated (run once even if notified multiple times), while tasks can run multiple times.
Was this helpful?
05
Can I use variables in handler names?
Yes, handler names can include variables, but it's not recommended because it can make debugging harder. If you do, ensure the variable resolves to a consistent value.
Was this helpful?
06
How do I debug why a handler didn't run?
Run with -v to see notification messages. Check if the notifying task reported 'changed'. Verify the handler name is correct and unique. Use --list-handlers to ensure the handler is defined. Check if any task failed before the handler would have run.
Was this helpful?
07
Can handlers be used in pre_tasks or post_tasks?
Yes, handlers can be notified from any task section (pre_tasks, tasks, post_tasks). They always run at the end of the play unless flushed.
Was this helpful?
08
Is it possible to have a handler that runs only on certain hosts?
Yes, you can add a when condition to a handler to limit it to specific hosts or groups.