Introduction
Ansible handlers are special tasks that run only when notified by other tasks, typically to restart services after configuration changes. When handlers fail to trigger, services continue running with stale configuration, causing inconsistent system states. The problem is often subtle: the playbook reports success but the expected restart never occurs, leaving you wondering why configuration changes don't take effect.
Handler issues are particularly insidious because they often produce no error messages. The handler simply doesn't run, and you only discover the problem when the service misbehaves with old configuration.
Symptoms
Handler shows as skipped despite task reporting changed:
``` TASK [Update nginx configuration] ********** changed: [web-server-01]
RUNNING HANDLER [Restart nginx] ************ skipping: [web-server-01] ```
Handler not found error:
ERROR! The requested handler 'Restart Nginx' was not found in any of the handlers directoriesHandler runs in wrong order:
``` TASK [Configure app] *************** changed: [server-01]
TASK [Configure database] ************** changed: [server-01]
RUNNING HANDLER [Restart app] ********** changed: [server-01]
RUNNING HANDLER [Restart database] ********* changed: [server-01] # Database restarted before app, causing connection failures ```
Handler doesn't run after playbook failure:
``` TASK [Update config] *************** changed: [server-01]
TASK [Start service] *************** fatal: [server-01]: FAILED! => {"msg": "Service failed to start"}
# Handler 'Restart service' was notified but never ran due to failure # Config changed but service not restarted - system in inconsistent state ```
Multiple handlers with similar names causing confusion:
```yaml handlers: - name: restart nginx service: name: nginx state: restarted
- name: Restart Nginx
- service:
- name: nginx
- state: reloaded
# Which one runs when you notify "restart nginx"? ```
Common Causes
1. Name Mismatch Between Notify and Handler
The notify directive must match the handler name exactly (case-sensitive):
```yaml tasks: - name: Update config copy: src: nginx.conf dest: /etc/nginx/nginx.conf notify: restart nginx # lowercase
handlers: - name: Restart Nginx # Mixed case - doesn't match! service: name: nginx state: restarted ```
2. Handler Defined in Wrong Scope
Handlers must be at the play level, not nested in tasks:
```yaml # WRONG - handlers inside tasks block - hosts: webservers tasks: - name: Update config copy: ... notify: restart nginx handlers: # INVALID - handlers can't be here - name: restart nginx service: ...
# CORRECT - handlers at play level - hosts: webservers handlers: - name: restart nginx service: name: nginx state: restarted tasks: - name: Update config copy: ... notify: restart nginx ```
3. Handler Never Notified Because Task Didn't Change
Tasks only notify handlers when they report changed:
```yaml - name: Check config stat: path: /etc/nginx/nginx.conf notify: restart nginx # stat always returns ok, never changed # Handler never notified
- name: Update config
- copy:
- src: nginx.conf
- dest: /etc/nginx/nginx.conf
- notify: restart nginx
- # If file already matches source, reports ok
- # Handler not notified because no change occurred
`
4. Playbook Failure Prevents Handler Execution
By default, handlers don't run if the play fails:
```yaml - hosts: all handlers: - name: restart service service: name: myapp state: restarted
tasks: - name: Update config copy: ... notify: restart service # Handler queued
- name: Start dependent service
- service:
- name: dependency
- state: started
- # If this fails, the queued handler never runs
`
5. Handler in Different Play
Handlers only apply to the play they're defined in:
```yaml - hosts: dbservers handlers: - name: restart postgres service: name: postgresql state: restarted tasks: - name: Configure postgres # ...
- hosts: webservers
- tasks:
- - name: Update app config
- copy: ...
- notify: restart postgres # ERROR: Handler not in this play!
`
6. Handler File Not Included
When using handler files, they must be explicitly included:
```yaml # handlers/main.yml - name: restart nginx service: name: nginx state: restarted
# playbook.yml - hosts: webservers # Missing: handlers directive tasks: - name: Update config copy: ... notify: restart nginx # Handler file not loaded ```
Step-by-Step Fix
Step 1: Verify Handler Name Matches Exactly
Check for name mismatches:
```bash # Extract handler names from playbook grep -A1 "handlers:" playbook.yml | grep "name:"
# Extract notify directives grep "notify:" playbook.yml
# Compare for exact matches (case-sensitive) ```
Create a handler verification task:
- name: Verify handler names
debug:
msg: "Notifying: {{ item }}"
loop:
- "restart nginx"
- "reload apache"
when: false # Disable after verificationStep 2: Fix Handler Definition and Scope
Correct playbook structure:
```yaml - hosts: webservers # Handlers at play level handlers: - name: Restart Nginx service: name: nginx state: restarted listen: "web services restart"
- name: Restart PHP-FPM
- service:
- name: php-fpm
- state: restarted
- listen: "web services restart"
tasks: - name: Update nginx config copy: src: nginx.conf dest: /etc/nginx/nginx.conf notify: Restart Nginx # Exact match
- name: Update PHP config
- copy:
- src: php.ini
- dest: /etc/php.ini
- notify: "web services restart" # Notifies all listeners
`
Step 3: Use force-handlers for Resilient Execution
Ensure handlers run even on failure:
```bash # Force handlers to run even if tasks fail ansible-playbook deploy.yml --force-handlers
# Or in playbook, use block/rescue with handler flush ```
```yaml - hosts: all handlers: - name: restart service service: name: myapp state: restarted
tasks: - block: - name: Update config copy: ... notify: restart service
- name: Risky operation
- command: /opt/app/migrate.sh
- # If this fails, handler still runs
rescue: - name: Flush handlers before rescue meta: flush_handlers
- name: Rollback config
- copy:
- src: config.yml.bak
- dest: /etc/app/config.yml
always: - name: Ensure handlers run meta: flush_handlers ```
Step 4: Force Handler Execution at Specific Points
Use meta: flush_handlers to control when handlers run:
```yaml - hosts: webservers handlers: - name: restart nginx service: name: nginx state: restarted
tasks: - name: Update nginx config copy: ... notify: restart nginx
- name: Flush handlers immediately
- meta: flush_handlers
- # nginx restarts here, before next task
- name: Wait for nginx
- wait_for:
- port: 80
- delay: 2
- name: Deploy application
- copy: ...
- # Application deployed to already-restarted nginx
`
Step 5: Implement Handler Debugging
Add debugging to identify handler issues:
```yaml - hosts: all handlers: - name: Restart Service block: - name: Log handler execution debug: msg: "Handler 'Restart Service' is executing"
- name: Restart the service
- service:
- name: myapp
- state: restarted
- register: restart_result
- name: Log result
- debug:
- msg: "Service restart: {{ restart_result.state }}"
tasks: - name: Update config copy: ... register: config_result notify: Restart Service
- name: Debug task result
- debug:
- msg:
- - "Config changed: {{ config_result.changed }}"
- - "Handler notified: {{ config_result.changed }}"
`
Step 6: Use Handler Lists for Multiple Notifications
Notify multiple handlers efficiently:
```yaml - hosts: all handlers: - name: restart nginx service: name: nginx state: restarted
- name: restart php-fpm
- service:
- name: php-fpm
- state: restarted
- name: clear cache
- command: rm -rf /var/cache/app/*
tasks: - name: Update application copy: ... notify: - restart nginx - restart php-fpm - clear cache ```
Or use the listen directive:
```yaml handlers: - name: restart nginx service: name: nginx state: restarted listen: "application updated"
- name: restart php-fpm
- service:
- name: php-fpm
- state: restarted
- listen: "application updated"
- name: clear opcache
- uri:
- url: http://localhost/opcache-reset.php
- listen: "application updated"
tasks: - name: Deploy application copy: ... notify: "application updated" # Triggers all three handlers ```
Step 7: Create Handler Validation Playbook
Test handler configuration:
```yaml # test_handlers.yml - name: Test handler configuration hosts: localhost gather_facts: false vars: playbook_file: site.yml
tasks: - name: Parse playbook set_fact: playbook_content: "{{ lookup('file', playbook_file) | from_yaml }}"
- name: Extract handler names
- set_fact:
- handler_names: "{{ playbook_content | selectattr('handlers', 'defined') | map(attribute='handlers') | flatten | map(attribute='name') | list }}"
- when: playbook_content | selectattr('handlers', 'defined') | list | length > 0
- name: Extract notify directives
- set_fact:
- notify_values: []
- name: Check each play for notify values
- set_fact:
- notify_values: "{{ notify_values + [item.tasks | selectattr('notify', 'defined') | map(attribute='notify') | flatten | list] }}"
- loop: "{{ playbook_content }}"
- when: item.tasks is defined
- name: Flatten notify values
- set_fact:
- all_notifies: "{{ notify_values | flatten }}"
- name: Find orphaned notifies
- debug:
- msg: "WARNING: Notify '{{ item }}' has no matching handler"
- loop: "{{ all_notifies }}"
- when: item not in handler_names | default([])
- name: Handler configuration check
- debug:
- msg:
- - "Defined handlers: {{ handler_names | default([]) }}"
- - "Notify directives: {{ all_notifies }}"
`
Verification
Test handler execution:
```bash # Run with verbose output to see handler execution ansible-playbook deploy.yml -v
# Should see: # RUNNING HANDLER [Restart nginx] ********** # changed: [web-server-01]
# Check the service was actually restarted ssh web-server-01 "systemctl status nginx" # Active: active (running) since <recent timestamp>
# Verify configuration is in effect ssh web-server-01 "nginx -T | grep <config_option>" ```
Test force-handlers:
```bash # Intentionally create a failing task after handler notification # Then run with --force-handlers ansible-playbook deploy.yml --force-handlers
# Verify handler ran despite failure # Service should be restarted even if playbook failed ```
Related Issues
- [ansible-handler-not-notified-task-changed-never-triggers](/articles/ansible-handler-not-notified-task-changed-never-triggers) - Task change detection issues
- [ansible-configuration-change-does-not-apply-until-full-restart](/articles/ansible-configuration-change-does-not-apply-until-full-restart) - Service restart issues
- [ansible-service-module-fails-to-start](/articles/ansible-service-module-fails-to-start) - Service management problems
Related Articles
- [WordPress troubleshooting: Ansible Artifact Download Uses an Old Mi](ansible-artifact-download-uses-an-old-mirror-after-proxy-change)
- [WordPress troubleshooting: Ansible Audit Trail Misses Events Under ](ansible-audit-trail-misses-events-under-burst-load)
- [WordPress troubleshooting: Ansible Background Worker Gets Stuck in ](ansible-background-worker-stuck-in-a-retry-loop)
- [WordPress troubleshooting: Ansible Backup Completes but Restore Fai](ansible-backup-completes-but-restore-fails-checksum-validation)
- [WordPress troubleshooting: Ansible Batch Importer Duplicates Rows A](ansible-batch-importer-duplicates-rows-after-a-retry)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "WordPress troubleshooting: Ansible Handlers Not Triggered - Notify ", "description": "Learn how to fix Ansible Handlers Not Triggered - Notify Not Working. Professional WordPress troubleshooting solutions with step-by-step guidance. WP error fix, WordPress optimization, WP security, WordPress performance.", "url": "https://www.fixwikihub.com/ansible-handler-not-triggered-notify-missing", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2025-12-15T08:54:09.999Z", "dateModified": "2025-12-15T08:54:09.999Z" } </script>