What are Ansible handlers, and when do they run?
Quick Answer
Handlers are special tasks that only run when another task notifies them via the 'notify' keyword. They run once at the end of a play, no matter how many tasks notify them, and are most commonly used for restarting services after config changes.
Detailed Answer
Think of handlers like a restaurant's 'order up' bell. Multiple cooks might finish different parts of the same order, but the waiter only needs to come to the window once to pick up the complete plate. Similarly, you might update three different nginx config files in a play, and each change notifies the 'restart nginx' handler, but the handler only fires once at the end -- you do not want nginx restarting three separate times in the middle of your deployment.
Handlers are defined in the handlers section of a playbook or in a role's handlers/main.yml file. They look just like regular tasks but only run when a task with a matching notify keyword reports a 'changed' status. The notify keyword takes the handler's name as a string (or a list of names), and Ansible queues the handler internally. If a task reports 'ok' (nothing changed), the notification is not sent and the handler does not run. This tight link between change detection and handler execution is what makes handlers so useful for configuration management.
Under the hood, Ansible keeps a notification queue for each play. When a task with a notify keyword changes the remote system, the handler name gets added to this queue. Duplicate notifications are automatically removed -- if five tasks all notify 'restart nginx,' it shows up only once in the queue. At the end of all tasks in the play, Ansible processes the queue in the order handlers are defined, not the order they were notified. You can force handlers to run mid-play using the meta: flush_handlers task, which drains the current queue and runs all pending handlers immediately before moving to the next task.
In production, handlers are essential for zero-downtime deployments and efficient service management. Picture a rolling deployment where you update the application binary, the config file, and the systemd unit file. Each of these tasks notifies the restart handler, but the service restarts only once after all three changes are applied. Without handlers, you would either restart after every change (causing unnecessary downtime) or need complicated conditional logic to restart only at the end. Handlers also support listen groups, where multiple handlers subscribe to a single notification topic, letting you trigger a chain of related actions from one notify.
A critical pitfall is that handlers do not run if the play fails before reaching the handler phase. If task 3 of 5 fails and task 2 notified a handler, that handler will not run by default. You can change this with the --force-handlers flag or by setting force_handlers: true in ansible.cfg, but this can be risky if the handler depends on later tasks completing successfully. Another pitfall is handler naming -- handlers are matched by exact name string, so a typo in either the notify or handler name silently fails with no error message. Always test that your handlers actually fire by checking the play recap for handler task names.