// learn Β· linux Β· rhce Β· 8 min

Ansible handlers.

A handler reacts to change: it restarts or reloads a service only when a task actually changed something β€” once, at the end of the play. Watch a changed task drop its handler into the notify queue, the queue dedupe, and the flush fire it exactly once.

Ansible handlers and notifications animated tutorial. Using notify on a task to trigger a handler only when the task reports changed, defining handlers in the play handlers section or a role handlers/main.yml, why a handler runs once at the end of the play in defined order, meta flush_handlers to run queued handlers mid-play, the failure gotcha where queued handlers are skipped and --force-handlers / force_handlers to run them anyway, and listen topics to fan one notify out to many handlers. RHCE EX294 ready.
// ansible Β· handlers & notifications

A handler restarts the service only when the config actually changed β€” once, at the end of the play. Watch a changed task drop its handler into the notify queue, the queue dedupe, and the flush fire it exactly once.

// before you start
you should know
  • Β·You can write a basic Ansible play with tasks
  • Β·Familiar with the template / copy / service modules
  • Β·Understand changed vs ok task results
by the end you'll

Use notify and handlers to restart or reload a service only on change, understand that handlers run once at the end of the play in defined order, handle the failure gotcha with --force-handlers, and fan out with listen topics.

pace: 8 minutes

ansible β€” step 1 / 5 Β· the idea
tasks:
deploy httpd.conf [template]● changed
notify β†’ restart httpd
open firewall [firewalld]β—‹ ok
↓ notify ↓
notify queue
empty
↓ flush ↓
handlers: (end of play)
πŸ”” restart httpdβ—‹ idle
πŸ”” reload firewalldβ—‹ idle

Why handlers exist

You only want to restart a service when its config actually changed β€” not on every single run. A HANDLER is a task that runs only when NOTIFIED, and only at the end of the play. Define handlers in the play's `handlers:` section (or, for a role, in roles/NAME/handlers/main.yml). A regular task adds `notify:` pointing at a handler by its exact name.

$tasks: - template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: restart httpd
$handlers: - name: restart httpd service: name: httpd state: restarted
$# name must match EXACTLY notify: restart httpd β†’ - name: restart httpd
// key insight

A handler is a task that runs only when something actually CHANGED, exactly ONCE, at the END of the play, in the order it is DEFINED. That single rule is what lets you say "restart the service" without restarting it on every single run.

// exam-ready Β· notify, handlers, flush & listen
$handlers:
$- name: restart httpd β†’ service
$notify: restart httpd
$notify: [ h1, h2 ]
$fires only when task is changed
$runs ONCE even if notified many times
$runs at END of play
$in DEFINED order
$- meta: flush_handlers
$listen: "topic" + notify: "topic"
$--force-handlers / force_handlers: true
$not notified β†’ handler skipped
// check yourself
4 quick questions
Q1

When does a handler actually run?

Q2

Five different tasks all `notify: restart httpd`, and all five report changed. How many times does httpd restart?

Q3

Two handlers are both queued. In what order do they run?

Q4

A task notifies `restart httpd`, then a later task FAILS before the play ends. What happens to the queued handler?

These aren't graded β€” they're just for active recall, which is what actually makes the lesson stick.

πŸ“¦
// next: reuse

Ansible roles

Tasks, handlers, templates, and defaults belong together. Package them into a reusable role so the handlers you just learned ride along automatically wherever the role is used.

open β†’

Read this lesson as text5 chapters Β· quiz answers

  1. 01Why handlers exist

    You only want to restart a service when its config actually changed β€” not on every single run. A HANDLER is a task that runs only when NOTIFIED, and only at the end of the play. Define handlers in the play's handlers: section (or, for a role, in roles/NAME/handlers/main.yml). A regular task adds notify: pointing at a handler by its exact name.

    • $ tasks: - template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: restart httpd β€” task points at a handler by name
    • $ handlers: - name: restart httpd service: name: httpd state: restarted β€” the matching handler block
    • $ # name must match EXACTLY notify: restart httpd β†’ - name: restart httpd β€” a typo = handler never runs
  2. 02notify β€” trigger on change

    A task fires its notify ONLY if it reports changed. If the run is ok (the system already matched), nothing is notified and the handler stays idle. A single task can notify several handlers by giving notify a list. Here the template CHANGED (amber), so it queues restart httpd; the firewall task was already correct, so it queues nothing.

    • $ - template: src: httpd.conf.j2 dest: /etc/httpd/conf/httpd.conf notify: restart httpd β€” changed β†’ queues the handler
    • $ - copy: src: app.conf dest: /etc/app.conf notify: - restart httpd - reload firewalld β€” notify a LIST of handlers
    • $ - firewalld: service: http state: enabled permanent: true notify: reload firewalld # ok β†’ no notify β€” unchanged task notifies nothing
  3. 03Run once, at the end, in defined order

    No matter how many tasks notify the same handler, it runs ONCE. Handlers run AFTER every task in the play, in the ORDER THEY ARE DEFINED in handlers: β€” not the order they were notified. Two tasks both notified restart httpd, but the queue is deduped, so httpd restarts a single time at the flush. Need it sooner? meta: flush_handlers runs the queue right now, mid-play.

    • $ - template: { src: httpd.conf.j2, dest: /etc/httpd/conf/httpd.conf } notify: restart httpd - copy: { src: vhost.conf, dest: /etc/httpd/conf.d/ } notify: restart httpd β€” two notify β†’ still ONE restart
    • $ handlers: - name: restart httpd # runs first - name: reload firewalld # runs second β€” DEFINED order, not notified order
    • $ - meta: flush_handlers β€” run queued handlers immediately, mid-play
  4. 04Gotchas β€” failures skip handlers

    If a later task FAILS before the play reaches the flush, the queued handlers do NOT run β€” the change you made (the new config) is left un-applied, so the service keeps running the old config. --force-handlers on the command line (or force_handlers: true in the play) runs any notified handlers even after a failure. Useful, but know the risk: you may restart into a half-finished change.

    • $ ansible-playbook site.yml --force-handlers β€” run notified handlers even after a failure
    • $ - hosts: web force_handlers: true tasks: ... β€” same, set per-play in YAML
    • $ # risk: handler restarts into a # half-applied change β€” decide deliberately β€” forcing is not always safe
  5. 05listen β€” fan out one notify to many handlers

    Instead of notifying handlers by name, you can notify a TOPIC: every handler whose listen: matches that topic runs. This decouples the task from the handler names, so one notify can fan out to a whole group of handlers (great in roles). You can mix styles β€” a handler can have a name, a listen, or both.

    • $ - template: { src: stack.j2, dest: /etc/stack.conf } notify: "restart web stack" β€” notify a TOPIC, not a name
    • $ handlers: - name: restart httpd service: { name: httpd, state: restarted } listen: "restart web stack" β€” handler subscribes to the topic
    • $ - name: reload firewalld service: { name: firewalld, state: reloaded } listen: "restart web stack" β€” both fire on one notify

Quiz β€” answers

When does a handler actually run?
β†’At the end of the play, and only if it was notified by a task that reported changed.Handlers are deferred: they queue when a task reports changed and notifies them, then run together after all tasks in the play. An ok (unchanged) task notifies nothing, so its handler never runs.
Five different tasks all notify: restart httpd, and all five report changed. How many times does httpd restart?
β†’One β€” the queue is deduped, so a handler runs once no matter how many times it was notified.The notify queue is deduplicated by handler. However many tasks notify restart httpd, it runs exactly once at the flush β€” that's the whole point of handlers.
Two handlers are both queued. In what order do they run?
β†’The order they are DEFINED in the handlers section β€” not the order they were notified.Run order follows the definition order in handlers: (or the role's handlers/main.yml), regardless of which task notified which handler first. If order matters, order your handler definitions.
A task notifies restart httpd, then a later task FAILS before the play ends. What happens to the queued handler?
β†’It doesn't run β€” queued handlers are skipped on failure β€” unless you use --force-handlers / force_handlers: true.A failure before the flush aborts the play, leaving notified handlers un-run β€” so your new config sits there un-applied. --force-handlers (or force_handlers: true) forces notified handlers to run even after a failure.
// more in systems

keep going β€” these pair well with what you just learned.

see all systems β†’

See also

back to RHCSA / RHCE trackall lessons