Ansible Handlers: Trigger Service Restarts on Change (Guide)
By Luca Berton · Published 2024-01-01 · Category: installation
Complete guide to Ansible handlers. Trigger service restarts, flush handlers, use listen, and manage handler execution order with practical examples.
Ansible handlers are a powerful feature that enhance efficiency and control in automation workflows. They allow tasks to trigger actions only when necessary, reducing redundancy and improving performance. This article explores what handlers are, how they work, and their best practices.
What Are Ansible Handlers?
Ansible handlers are special tasks that are executed only when notified by other tasks. They are typically used to perform actions like restarting services or reloading configurations after a change has been made.
Key Features:
• Triggered Execution: Run only when explicitly notified. • Task Efficiency: Avoid redundant actions, such as unnecessary service restarts. • Reuse: Define handlers once and use them across multiple tasks.See also: Can Ansible Be Used to Manage Windows Systems?
How Do Handlers Work?
Handlers are defined just like tasks but are listed under a dedicated handlers section in a playbook. A task notifies a handler to run when it changes something.
Example: Restarting a Service
- name: Install and configure Apache
hosts: webservers
tasks:
- name: Install Apache
apt:
name: apache2
state: present
notify: Restart Apache
- name: Configure Apache
template:
src: apache.conf.j2
dest: /etc/apache2/apache2.conf
notify: Restart Apache
handlers:
- name: Restart Apache
service:
name: apache2
state: restarted
How It Works:
Tasks notify the handler (Restart Apache) only if they make changes.
At the end of the play, Ansible runs the handler once, regardless of how many times it was notified.
Key Concepts of Handlers
Idempotency: Handlers, like tasks, are idempotent, ensuring consistent results. Execution Order: Handlers are executed after all tasks in the play are completed. Notification Frequency: A handler is triggered only once per play, even if multiple tasks notify it. Conditional Handlers: You can usewhen statements to control handler execution:
handlers:
- name: Restart Apache
service:
name: apache2
state: restarted
when: apache_needs_restart == true
See also: Can Ansible Manage Windows? Complete Windows Automation Guide
Benefits of Using Handlers
• Performance: Avoid redundant executions by running handlers only when needed. • Modularity: Simplify playbooks by separating actions from tasks. • Consistency: Ensure actions are triggered only under specific conditions.Common Use Cases for Handlers
Service Management: Restart or reload services after configuration changes. Database Management: Reload databases after schema updates. Application Deployment: Trigger actions like clearing caches or reloading environments.See also: Ansible on Windows: Complete Guide to Windows Automation (2026)
Best Practices for Handlers
Use Meaningful Names: Clearly describe the action the handler performs, such asRestart Apache.
Group Notifications:
Consolidate tasks that notify the same handler to avoid multiple triggers.
Minimize Side Effects:
Ensure handlers perform only the necessary actions to maintain idempotency.
Test Handler Logic:
Validate handlers in a test environment to ensure proper execution.
Conclusion
Ansible handlers are an essential tool for managing dynamic, event-driven actions in playbooks. By leveraging handlers, you can enhance automation efficiency, reduce redundancy, and maintain clean, modular playbooks.
Learn More About Ansible Handlers
How Handlers Work
Handlers are tasks that only run when notified by another task that reports a change:
tasks:
- name: Update config
ansible.builtin.template:
src: app.conf.j2
dest: /etc/myapp/app.conf
notify: restart myapp # Only notifies if file changed
handlers:
- name: restart myapp
ansible.builtin.service:
name: myapp
state: restarted
Handler Rules
| Rule | Description |
|------|-------------|
| Run once | Even if notified multiple times |
| Run at end | After all tasks complete (not immediately) |
| Run in order | Order defined in handlers section |
| Skip if no change | Only triggered by changed status |
Multiple Handlers
tasks:
- name: Update nginx config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- validate nginx
- reload nginx
handlers:
- name: validate nginx
command: nginx -t
changed_when: false
- name: reload nginx
service:
name: nginx
state: reloaded
Listen Groups
tasks:
- name: Deploy application
git:
repo: https://github.com/myorg/app.git
dest: /opt/app
notify: deploy complete
handlers:
- name: Install dependencies
command: pip install -r /opt/app/requirements.txt
listen: deploy complete
- name: Run migrations
command: python /opt/app/manage.py migrate
listen: deploy complete
- name: Restart application
service:
name: myapp
state: restarted
listen: deploy complete
Flush Handlers (Run Immediately)
tasks:
- name: Update config
template:
src: config.j2
dest: /etc/myapp/config.yml
notify: restart myapp
- meta: flush_handlers # Restart happens NOW
- name: Verify service is running
uri:
url: http://localhost:8080/health
retries: 5
delay: 3
Handlers in Roles
# roles/webserver/tasks/main.yml
- name: Deploy site config
template:
src: site.conf.j2
dest: /etc/nginx/sites-enabled/mysite.conf
notify: reload nginx
# roles/webserver/handlers/main.yml
- name: reload nginx
service:
name: nginx
state: reloaded
Handlers vs Regular Tasks
| Feature | Handler | Task |
|---------|---------|------|
| When runs | Only on change notification | Always (unless when:) |
| Run count | Once per play (deduplicated) | Every time |
| Timing | End of play (or flush_handlers) | In sequence |
| Triggered by | notify keyword | Play execution order |
FAQ
Why didn't my handler run?
The notifying task reportedok (no change), not changed
The handler name doesn't match the notify string exactly
The play failed before handlers ran (use --force-handlers to override)
Can I force handlers to always run?
ansible-playbook site.yml --force-handlers
This runs notified handlers even if later tasks fail.
How do I notify a handler from a role?
Use the handler's full name: notify: "role_name : handler_name" or use listen groups for cross-role notifications.
Basic Handler
- hosts: webservers
tasks:
- name: Update nginx config
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
become: true
handlers:
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
become: true
Handlers run once at the end of the play, only if notified.
Multiple Notifications
tasks:
- template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify:
- restart nginx
- reload firewall
become: true
handlers:
- name: restart nginx
service: { name: nginx, state: restarted }
become: true
- name: reload firewall
service: { name: firewalld, state: reloaded }
become: true
Listen (Group Notifications)
tasks:
- template:
src: ssl-cert.j2
dest: /etc/ssl/app.crt
notify: ssl changed
handlers:
- name: restart nginx
service: { name: nginx, state: restarted }
listen: ssl changed
become: true
- name: restart haproxy
service: { name: haproxy, state: restarted }
listen: ssl changed
become: true
Flush Handlers (Run Immediately)
tasks:
- template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
# Force handlers to run NOW (before next task)
- meta: flush_handlers
- uri:
url: http://localhost/health
status_code: 200
Handler Execution Order
# Handlers run in DEFINITION order, not notification order
handlers:
- name: restart nginx # Runs first (defined first)
service: { name: nginx, state: restarted }
- name: clear cache # Runs second
command: /opt/clear-cache.sh
- name: send notification # Runs third
uri:
url: https://hooks.slack.com/...
method: POST
body: '{"text": "Deploy complete"}'
Handler in Roles
# roles/webserver/handlers/main.yml
- name: restart nginx
service:
name: nginx
state: restarted
become: true
- name: reload nginx
service:
name: nginx
state: reloaded
become: true
# roles/webserver/tasks/main.yml
- template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx # References handler in same role
Conditional Handlers
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
when: ansible_os_family == "Debian"
become: true
Handler with Variables
handlers:
- name: restart app
service:
name: "{{ app_service_name }}"
state: restarted
become: true
Common Patterns
# Reload vs restart
handlers:
- name: reload nginx # Graceful — no downtime
service: { name: nginx, state: reloaded }
- name: restart nginx # Full restart — brief downtime
service: { name: nginx, state: restarted }
# Chain handlers
tasks:
- template: { src: app.conf.j2, dest: /etc/app.conf }
notify: validate and restart
handlers:
- name: validate and restart
command: /opt/app/validate-config.sh
notify: restart app
- name: restart app
service: { name: myapp, state: restarted }
Handler Pitfalls
# Handler NOT triggered if task is "ok" (no change)
- copy:
src: config.conf
dest: /etc/app/config.conf
notify: restart app
# If file already matches → no change → handler NOT called
# Handler runs only ONCE even if notified multiple times
- template: { src: a.conf.j2, dest: /etc/a.conf }
notify: restart app
- template: { src: b.conf.j2, dest: /etc/b.conf }
notify: restart app
# restart app runs once, not twice
FAQ
When do handlers run?
At the end of each play (after all tasks), or when explicitly flushed with meta: flush_handlers.
Do handlers run if a task fails?
No — by default, handlers don't run if any task fails. Use --force-handlers or force_handlers: true to override.
Can I call a handler from another handler?
Yes — use notify in the handler definition. They chain in definition order.
Handler vs post_tasks?
Handlers are conditional (only run on change). post_tasks always run after roles and tasks.
Related Articles
• the Ansible template module reference • Ansible conditional patterns • handler ordering in AnsibleCategory: installation