unit 5 · Logic
Handlers, notify & listen
Ten config changes, one restart
you may want to read 12 · Playbook anatomy first — but nothing is locked
tl;dr
- notify: queues a handler — a task that runs only when the notifying task reports changed.
- Queued handlers run once, at the end of the play, however many tasks notified them.
- listen: turns the notify string into a topic several handlers can subscribe to.
why it matters Restarting a service every run is needless downtime; never restarting leaves stale config live — handlers restart exactly when something changed, and only once.
Lesson 12’s playbook shipped with a hole it admitted to: nothing reacted
when the copy changed a file. Point that copy at nginx’s own config and the
hole bites. A running nginx serves the config it loaded at startup.
Rewrite the file and the process ignores it. state: started
won’t help; it sees the service up and says ok.
Both naive fixes are bad. state: restarted bounces a healthy service on
every run — downtime for nothing, and a permanent changed in your recap.
Registering the copy result and gating a restart on it works, but it’s
plumbing for a pattern so common Ansible built it in: the
handler .
A handler is a task like any other — name, module, args — written in a
play’s handlers: section instead of tasks:. It doesn’t run in order
with the play. It runs only when a task notify -s it, and
a task only notifies when it reports changed:
- name: Configure web servers
hosts: web
become: true
tasks:
- name: Push nginx config # changed → queues the handler
ansible.builtin.copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
notify: Restart nginx # must match the handler's name exactly
handlers:
- name: Restart nginx # runs ONCE, after the last task
ansible.builtin.service:
name: nginx
state: restartedRead the flow. copy compares the file; when it rewrites it — changed —
the notification is queued. ok queues nothing. Run it again with the file
already correct: ok, no notification, no restart. Restart-on-change,
idempotently, for one extra line per task.
Once, at the end
The design insight: notifications are batched and deduplicated. A
handler doesn’t run when notified. Every queued handler runs after the
last task of the play, once each, no matter how many tasks notified it. A
play that templates ten nginx config files, each notifying
Restart nginx, restarts nginx exactly once.
Two mechanics to burn in. Matching is by exact string: notify: must
equal the handler’s name: character for character, and notifying a
handler that doesn’t exist fails the run. Handlers execute in the order
they’re defined under handlers:, not the order they were notified.
And when end-of-play is too late — a later task needs the service already
restarted — drain the queue early with a meta: flush_handlers task.
One notification, many handlers
notify matches names one-to-one. When a single change should trigger
several reactions, listen: turns the string into a topic that any number
of handlers subscribe to:
handlers:
- name: Restart nginx
ansible.builtin.service:
name: nginx
state: restarted
listen: web stack changed # subscribes to the topic
- name: Flush app cache
ansible.builtin.command: /usr/local/bin/flush-cache
listen: web stack changed # same topic, also firesThe notifying task writes notify: web stack changed once. Every
subscribed handler runs, in definition order. This earns its keep in
lesson 24, where roles ship handlers in their own handlers/ directory —
a topic lets your play trigger a role’s reactions without knowing its
handler names.
One last boundary: the handler doesn’t replace lesson 12’s service task.
state: started still belongs in tasks: to guarantee the service is up
at all; the handler owns restarts. Tasks declare state, handlers react
to change — hold that split and every handlers: block you meet reads
instantly.
replay — Two notifications, one restart
a recording, not a simulator — tap ⓘ lines for what they mean
self-check — recall, not a test
1 A play templates ten nginx config files, each with notify: Restart nginx. How many restarts, and when?
2 You re-run the play; the config file is unchanged. Does the handler fire? Why not?
3 Your config task reported changed, a later task failed, and now re-runs never restart the service. What happened?
lab (optional — never required)
Take your lesson 12 site.yml, add a Restart nginx handler notified by the copy task, run twice, and confirm the handler fires on run one only.
needs: The control node from lesson 5.