AnsibleXVI · HandlersHandlers
listen topics as a handler interface
What you'll learn
- Use listen to fan one notification out to several handlers
- Explain why a topic is a stabler contract than a handler name
- Choose between notifying a name and notifying a topic
- Avoid the trap of templating a listen topic
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
notify: Reload nginx names an implementation. The task says which
handler to run, which means the task knows how the service is
restarted, what the handler is called, and — implicitly — that there is
exactly one of it.
listen inverts that. A handler declares which topic it subscribes to,
and a task notifies the topic. The task now says what needs to happen
and stays silent on how many handlers implement it or what they are
called.
The mechanism
- name: Listen topics and handler chaining
hosts: local
gather_facts: false
tasks:
- name: Notify a topic rather than a name
ansible.builtin.debug:
msg: t
changed_when: true
notify: restart web stack
handlers:
- name: Reload the cache layer
ansible.builtin.debug:
msg: cache reloaded
listen: restart web stack
- name: Reload the web server
ansible.builtin.debug:
msg: web reloaded
listen: restart web stack$ ansible-playbook -i inv.ini listen.ymlTASK [Notify a topic rather than a name] ***************************************
changed: [localhost]
RUNNING HANDLER [Reload the cache layer] ***************************************
ok: [localhost]
RUNNING HANDLER [Reload the web server] ****************************************
ok: [localhost]The handler names still appear in the output — that is what identifies them for a reader of the run log. Only the notification uses the topic.
A handler may carry both a name and a listen, and can then be
notified either way. It may also listen to more than one topic:
handlers:
- name: Reload nginx
ansible.builtin.systemd_service:
name: nginx
state: reloaded
listen:
- restart web stack
- tls material changedWhy a topic is a stabler contract
The operational argument is about who owns which side of the boundary.
Consider a webserver role with a handler called Reload nginx, and a
separate play or role whose tasks deploy TLS certificates and notify it.
The certificate tasks now depend on a string defined inside somebody
else’s role. When that role is refactored — nginx replaced with Caddy,
the handler renamed to Reload the reverse proxy, the reload split into
two steps — every notifier breaks.
And it breaks in the worst available way. A notify naming a handler that no longer exists aborts the run:
$ ansible-playbook -i inv.ini typo.ymlPLAY [A notify that names no handler] ******************************************
TASK [Deploy the configuration] ************************************************
[ERROR]: The requested handler 'Reload the sevrice' was not found in either the main handlers list nor in the listening handlers listAn immediate abort is at least visible. But it happens at run time on whichever host reaches that task first, potentially mid-rollout, with part of the fleet already changed.
With a topic, the role publishes restart web stack as its interface
and is free to rename, split or replace the handlers behind it. The
notifiers keep working because they never referred to an implementation
in the first place.
The fan-out is the point, and also the risk
One notification running four handlers is convenient. It is also four things happening on the host from one line of YAML that does not say so.
handlers:
- name: Reload nginx
ansible.builtin.systemd_service:
name: nginx
state: reloaded
listen: tls material changed
- name: Restart the API service
ansible.builtin.systemd_service:
name: billing-api
state: restarted
listen: tls material changed
- name: Restart the message broker
ansible.builtin.systemd_service:
name: broker
state: restarted
listen: tls material changednginx reloads without dropping connections. billing-api and broker
are full restarts. A certificate renewal that renders every 60 days has
just become a three-service restart across every host that renewed —
and the task that triggered it says only notify: tls material changed.
The rule that follows: the blast radius of a notify is the handlers section, not the notify line. When reviewing a change that adds a notify, read the subscribers.
Knowledge check
Knowledge check · 4 questions
Q1. A role you do not own defines a handler named Reload nginx. Your certificate tasks notify it by name. The role is refactored and the handler is renamed. What happens on the next run?
Q2. Two handlers subscribe to the topic restart web stack: Reload the cache layer, defined first, and Reload the web server, defined second. A task notifies the topic once. What runs?
Q3. Which of these are true of listen topics? Select all that apply.
Q4. Because a topic can fan out to several handlers, reading the notify line is enough to know what a change will do to the host.
Passing score: 75%. Answers are checked in this browser.