Introduction to Handlers
Handlers let you trigger specific tasks in response to changes made by other tasks. They’re useful when a configuration change needs a follow-up action, such as restarting a service, reloading a daemon, or applying a change that shouldn’t happen unless something actually changed. This is better built in design than is changed approach.
Defining A Handler
Handlers live in a play’s handlers: section, written with the exact same syntax as any other task:
- name: Handlers Demo
hosts: servers
tasks:
- name: Deploy configuration
ansible.builtin.copy:
src: app.conf
dest: /etc/app.conf
notify: Restart app
handlers:
- name: Restart app
ansible.builtin.debug:
msg: "Restarting the app now"notify: — Triggering A Handler
notify: names the handler to trigger — the string has to match the handler’s name: exactly. When the notifying task reports changed, that handler gets queued to run — not immediately, but deferred, as covered shortly.
Rule One: Handlers Only Run If The Notifying Task Changed
This is the payoff. Run the playbook above once, against a config file that doesn’t exist yet:
changed: [ubuntu]
ok: [ubuntu] => { "msg": "Restarting the app now" }Run it again, completely unchanged:
ok: [ubuntu]No handler output at all, the second time — copy correctly reported no change, so Restart app was never even queued. This is exactly the manual register + is changed pattern from the tests and conditionals section, now automatic: the handler mechanism itself only fires on genuine change, with nothing extra to write.
Rule Two: A Handler Runs Only Once, No Matter How Many Times It’s Notified
Multiple tasks can notify the same handler — and it still runs exactly once:
tasks:
- name: Deploy config A
ansible.builtin.copy:
src: a.conf
dest: /etc/a.conf
notify: Restart app
- name: Deploy config B
ansible.builtin.copy:
src: b.conf
dest: /etc/b.conf
notify: Restart app
handlers:
- name: Restart app
ansible.builtin.debug:
msg: "Restarting the app now"If both config files are new, both tasks report changed, and both notify Restart app — but the output still shows the handler firing only once, not twice. Ansible automatically deduplicates repeated notifications to the same handler within a single play run.
When Do Handlers Actually Run?
By default, handlers run at the end of the play, after every regular task has finished — not immediately after whichever task notified them. A task near the top of your task list and one near the bottom can both notify the same handler, and it still only runs once, at the very end. (There’s a way to change this timing, covered in the next chapter.)
Handlers And Loops: A Real Trip-Up
Here’s the assumption almost everyone makes, and it’s wrong: “if 3 out of 5 loop iterations changed something, the handler must run 3 times.” It doesn’t. It runs once, exactly the same as Rule Two above — a looped task is still one task, and however many of its iterations report changed, the handler it notifies fires only a single time.
- name: Create multiple config files
ansible.builtin.copy:
content: "config for {{ item }}"
dest: "/etc/{{ item }}.conf"
loop:
- app1
- app2
- app3
notify: Restart app
handlers:
- name: Restart app
ansible.builtin.debug:
msg: "Restarting the app now"Suppose app1.conf and app2.conf are new, and app3.conf already exists with identical content — two changed iterations, one unchanged. Restart app is still triggered by the loop overall (since some iteration changed), and it still only runs once at the end of the play, not twice for the two iterations that changed and not three times for every item in the loop. The mental model is exactly Rule Two: it doesn’t matter how a handler gets notified — one task, several tasks, or several iterations of one looped task — it collapses to a single execution regardless.
Best Practices
- Rely on Rule One instead of manually building
register+is changedlogic — that’s precisely the problem handlers exist to solve automatically. - Notify freely from multiple related tasks without worrying about redundant execution — Rule Two’s deduplication handles that for you.
- Never assume a looped task’s notify count scales with how many iterations changed — one loop, however many changed items, is still exactly one notification worth of effect.