Demystifying Ansible Handlers: Ensuring Idempotent Service Restarts
Learn how Ansible handlers restart or reload services only when tasks change, with examples for playbooks, roles, and flush handlers.
Demystifying Ansible Handlers: Ensuring Idempotent Service Restarts
Ansible handlers solve a common deployment problem: your config file may be checked on every run, but your service should restart only when that file actually changes. Without handlers, your playbook can restart healthy services for no reason.
This guide explains how handlers work, where to define them, and when to flush them early. The examples use web services, but the same pattern works for app workers, schedulers, and system daemons.
What Are Ansible Handlers?
In Ansible, a handler is a task that runs only after another task notifies it. When a task changes something and includes notify, Ansible queues the matching handler.
Key characteristics of handlers:
- Triggered by notification: A handler runs when a changed task uses
notify. - Run once per play: If five tasks notify the same handler, Ansible still runs it once at the end of the play.
- Best for restarts and reloads: Handlers are ideal for service actions that should happen only after configuration changes.
Why Use Handlers for Service Restarts?
The primary use case for Ansible handlers is service management. When you update an Apache, Nginx, or application config file, the service often needs a restart or reload. You only want that action when the deployed file differs from the current file.
Consider the alternative:
- Without handlers: A direct restart task runs every time the playbook reaches it, unless you add extra conditions.
- With handlers: The template or copy task notifies a restart only when it reports
changed.
For example, a production Nginx playbook might run every hour to enforce config drift. With handlers, unchanged config files do not cause hourly reloads.
How to Implement Ansible Handlers
Handlers live in a handlers section in a playbook or in handlers/main.yml inside a role. The handler name must match the name used by notify.
Basic Handler Syntax
Handlers are declared in a handlers block at the playbook level or within a role.
---
- name: Configure and restart web server
hosts: webservers
become: yes
tasks:
- name: Ensure Apache configuration is present
template:
src: templates/httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
notify:
- Restart Apache
handlers:
- name: Restart Apache
service:
name: httpd
state: restarted
In this example:
- A
templatetask is used to deploy a new Apache configuration file (httpd.conf). - The
notifykeyword is set toRestart Apache. This means if thetemplatetask successfully changes thehttpd.conffile, Ansible will signal the handler namedRestart Apache. - The
handlerssection defines theRestart Apachehandler, which uses theservicemodule to restart thehttpdservice.
Notifying Multiple Handlers
A single task can notify multiple handlers. This is useful if changing one configuration requires restarting multiple services or performing several cleanup actions.
---
- name: Deploy application with database and web server updates
hosts: app_servers
become: yes
tasks:
- name: Update application configuration
copy:
src: files/app.conf
dest: /etc/app/app.conf
notify:
- Restart application service
- Reload Nginx
handlers:
- name: Restart application service
service:
name: myapp
state: restarted
- name: Reload Nginx
service:
name: nginx
state: reloaded
In this scenario, if app.conf is updated, both the Restart application service and Reload Nginx handlers will be triggered.
Using Handlers in Roles
Handlers are commonly used within Ansible roles. They are defined in the handlers/main.yml file of a role. When a task within the role (or from a playbook that includes the role) notifies a handler defined in the role, Ansible will execute it.
Let's assume you have a role named apache with the following structure:
apache/
├── handlers/
│ └── main.yml
└── tasks/
└── main.yml
apache/tasks/main.yml:
---
- name: Deploy Apache configuration
template:
src: httpd.conf.j2
dest: /etc/httpd/conf/httpd.conf
notify:
- Restart Apache
apache/handlers/main.yml:
---
- name: Restart Apache
service:
name: httpd
state: restarted
Then, in your playbook, you would include the role:
---
- name: Configure web server using Apache role
hosts: webservers
become: yes
roles:
- apache
When the Deploy Apache configuration task in the apache role executes and modifies the configuration, the Restart Apache handler defined in apache/handlers/main.yml will be triggered.
Best Practices for Using Handlers
- Keep each handler focused on one action.
- Use names that match the action, such as
Reload Nginx. - Prefer
state: reloadedwhen the service supports reloads and a full restart is not needed. - Avoid direct restart tasks after config tasks.
- Remember that notified handlers run at the end of the play unless you flush them.
Advanced Concepts: Flushing Handlers
By default, handlers run once at the end of a play. Sometimes you need a restart before later tasks continue. For example, you may need to restart an app after writing its primary config before running a health check or migration.
---
- name: Perform sequential configuration updates requiring immediate service restarts
hosts: servers
become: yes
tasks:
- name: Update primary config file
copy:
src: files/primary.conf
dest: /etc/myapp/primary.conf
notify:
- Restart Myapp
- name: Flush handlers to apply immediate restart
meta: flush_handlers
- name: Update secondary config file
copy:
src: files/secondary.conf
dest: /etc/myapp/secondary.conf
notify:
- Restart Myapp
handlers:
- name: Restart Myapp
service:
name: myapp
state: restarted
Here, the first config change triggers Restart Myapp, and meta: flush_handlers runs it immediately. A later task can notify the same handler again if it changes another file.
When to See a Professional
Ask for a review when a handler restarts production databases, load balancers, or clustered services. Some systems need rolling restarts, quorum checks, or drain steps that should not be hidden behind a simple handler.
Takeaway
Use Ansible handlers whenever a changed task should trigger a service restart, reload, or daemon reload. They keep your playbooks idempotent, reduce unnecessary restarts, and make service changes easier to reason about.