AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible Handlers: Trigger Tasks on Change (Complete Guide)

By Luca Berton · Published 2026-04-03 · Category: troubleshooting

How to use Ansible handlers for event-driven task execution. Trigger handlers with notify, flush handlers, listen directive, multiple handlers.

Ansible handlers are special tasks that run only when triggered by a notify directive. They're perfect for actions like restarting services — you want them to run only if something actually changed.

Basic Handler Example

- name: Configure nginx
  hosts: webservers
  tasks:
    - name: Update nginx config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx

handlers: - name: restart nginx ansible.builtin.service: name: nginx state: restarted

The handler restart nginx only runs if the template task reports "changed".

See also: Ansible loop_control: Control Loop Output, Labels & Pauses (Guide)

How Handlers Work

A task with notify triggers the handler when the task status is changed Handlers are queued, not run immediately All handlers run once at the end of the play, in the order they're defined Even if notified multiple times, a handler runs only once

Multiple Notifications

tasks:
  - name: Update nginx.conf
    ansible.builtin.template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart nginx

- name: Update sites-enabled ansible.builtin.template: src: site.conf.j2 dest: /etc/nginx/sites-enabled/mysite.conf notify: restart nginx

# nginx restarts only ONCE even though notified twice

handlers: - name: restart nginx ansible.builtin.service: name: nginx state: restarted

See also: Ansible Handlers: Trigger Service Restarts on Change (Guide)

Notify Multiple Handlers

tasks:
  - name: Update SSL certificate
    ansible.builtin.copy:
      src: cert.pem
      dest: /etc/ssl/certs/app.pem
    notify:
      - restart nginx
      - restart app

handlers: - name: restart nginx ansible.builtin.service: name: nginx state: restarted

- name: restart app ansible.builtin.service: name: myapp state: restarted

listen: Group Handlers

Use listen to trigger multiple handlers with a single notification:

tasks:
  - name: Deploy new release
    ansible.builtin.unarchive:
      src: release.tar.gz
      dest: /opt/app
    notify: "deploy complete"

handlers: - name: clear cache ansible.builtin.command: /opt/app/clear-cache.sh listen: "deploy complete"

- name: restart app ansible.builtin.service: name: myapp state: restarted listen: "deploy complete"

- name: notify slack ansible.builtin.uri: url: https://hooks.slack.com/services/xxx method: POST body_format: json body: text: "Deployment complete on {{ inventory_hostname }}" listen: "deploy complete"

See also: Ansible Troubleshooting: Fix Jinja2 Syntax & Inventory Errors

flush_handlers: Run Handlers Mid-Play

tasks:
  - name: Update config
    ansible.builtin.template:
      src: app.conf.j2
      dest: /etc/myapp/app.conf
    notify: restart app

- name: Force handler execution now ansible.builtin.meta: flush_handlers

- name: Verify app is running (needs restart to complete first) ansible.builtin.uri: url: http://localhost:8080/health status_code: 200

Handler Execution Order

Handlers run in the order they're defined, not the order they're notified:

handlers:
  - name: stop app        # Runs first
    ansible.builtin.service:
      name: myapp
      state: stopped

- name: clear cache # Runs second ansible.builtin.command: rm -rf /tmp/cache

- name: start app # Runs third ansible.builtin.service: name: myapp state: started

Handlers in Roles

roles/
  nginx/
    tasks/
      main.yml
    handlers/
      main.yml    # Handlers auto-loaded from here
    templates/
      nginx.conf.j2
# roles/nginx/handlers/main.yml
- name: restart nginx
  ansible.builtin.service:
    name: nginx
    state: restarted

- name: reload nginx ansible.builtin.service: name: nginx state: reloaded

Common Patterns

Reload vs Restart

tasks:
  - name: Update main config (needs restart)
    ansible.builtin.template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart nginx

- name: Update site config (needs reload only) ansible.builtin.template: src: site.conf.j2 dest: /etc/nginx/sites-enabled/mysite.conf notify: reload nginx

handlers: - name: restart nginx ansible.builtin.service: name: nginx state: restarted

- name: reload nginx ansible.builtin.service: name: nginx state: reloaded

Validate before restart

handlers:
  - name: validate nginx config
    ansible.builtin.command: nginx -t
    listen: "restart nginx"

- name: restart nginx service ansible.builtin.service: name: nginx state: restarted listen: "restart nginx"

FAQ

Why doesn't my handler run?

The task must report changed status. If the task shows "ok" (no changes), the handler won't trigger. Check with -v for task status.

Can I force a handler to run?

Use changed_when: true on the notifying task, or call meta: flush_handlers followed by the handler task directly.

Do handlers run on failed plays?

No. If a play fails, pending handlers are skipped. Use --force-handlers or set force_handlers = True in ansible.cfg to override.

Can handlers notify other handlers?

Yes! A handler can include notify to chain handlers together.

Basic Handler

- hosts: webservers
  become: true
  tasks:
    - name: Update nginx config
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: restart nginx

handlers: - name: restart nginx ansible.builtin.service: name: nginx state: restarted

Multiple Notifiers

tasks:
  - template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart nginx

- copy: src: ssl.conf dest: /etc/nginx/conf.d/ssl.conf notify: restart nginx

# Handler runs ONCE even if notified multiple times handlers: - name: restart nginx service: name=nginx state=restarted

Multiple Handlers

tasks:
  - template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify:
      - validate nginx config
      - restart nginx

handlers: - name: validate nginx config command: nginx -t

- name: restart nginx service: name=nginx state=restarted

listen (Handler Groups)

tasks:
  - template:
      src: app.conf.j2
      dest: /etc/myapp/app.conf
    notify: restart app stack

handlers: - name: restart app service: name=myapp state=restarted listen: restart app stack

- name: restart worker service: name=myapp-worker state=restarted listen: restart app stack

- name: clear cache command: redis-cli FLUSHALL listen: restart app stack

Flush Handlers Mid-Play

tasks:
  - template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart nginx

# Force handler to run NOW (before next task) - meta: flush_handlers

- name: Verify nginx is running uri: url: http://localhost/health status_code: 200

Handler in Roles

# roles/nginx/tasks/main.yml
- template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: restart nginx

# roles/nginx/handlers/main.yml - name: restart nginx service: name: nginx state: restarted become: true

Handler Execution Order

Handlers run: At the end of the play (after all tasks) In the order defined in the handlers section Once regardless of how many tasks notify them Only if at least one task notified them and reported changed

Conditional Handlers

handlers:
  - name: restart nginx
    service:
      name: nginx
      state: restarted
    when: ansible_os_family == "Debian"

changed_when and Handlers

# Handler only fires when task reports changed
- command: /opt/scripts/check-config.sh
  register: check
  changed_when: "'updated' in check.stdout"
  notify: reload config

FAQ

Why didn't my handler run?

The notifying task didn't report changed (was already in desired state) The task failed before reaching the handler Handler name doesn't match notify string (case-sensitive!) Using --check mode (handlers don't run in check mode)

Can I force a handler to always run?

Use meta: flush_handlers or move the action to a regular task instead.

Handlers vs regular tasks?

Handlers are conditional — they only run when notified by a changed task. Use handlers for restarts/reloads that should only happen when configuration actually changes.

Basic Handler

tasks:
  - template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart nginx
    become: true

handlers: - name: restart nginx service: name: nginx state: restarted become: true

Multiple Notifications

tasks:
  - template: { src: nginx.conf.j2, dest: /etc/nginx/nginx.conf }
    notify:
      - validate nginx
      - restart nginx

handlers: - name: validate nginx command: nginx -t become: true

- name: restart nginx service: { name: nginx, state: restarted } become: true

listen (Topic-Based)

tasks:
  - template: { src: app.conf.j2, dest: /etc/myapp/config }
    notify: app config changed

handlers: # Multiple handlers respond to one topic - name: restart app service: { name: myapp, state: restarted } listen: app config changed become: true

- name: clear app cache file: { path: /tmp/myapp-cache, state: absent } listen: app config changed

flush_handlers

tasks:
  - template: { src: nginx.conf.j2, dest: /etc/nginx/nginx.conf }
    notify: restart nginx

# Force handlers to run NOW (before continuing) - meta: flush_handlers

- uri: url: http://localhost/health status_code: 200 retries: 5 delay: 2

Handler Ordering

# Handlers run in DEFINITION order, not notification order
handlers:
  - name: reload systemd    # Runs 1st (defined first)
    systemd: { daemon_reload: true }

- name: restart app # Runs 2nd service: { name: myapp, state: restarted }

- name: check health # Runs 3rd uri: { url: "http://localhost/health" }

Handler in Roles

# roles/nginx/handlers/main.yml
- name: restart nginx
  service: { name: nginx, state: restarted }
  become: true

- name: reload nginx service: { name: nginx, state: reloaded } become: true

# roles/nginx/tasks/main.yml - template: { src: nginx.conf.j2, dest: /etc/nginx/nginx.conf } notify: reload nginx

Conditional Handler

handlers:
  - name: restart nginx
    service: { name: nginx, state: restarted }
    become: true
    when: ansible_os_family == "Debian"

Handler with Block

tasks:
  - block:
      - template: { src: a.conf.j2, dest: /etc/a.conf }
      - template: { src: b.conf.j2, dest: /etc/b.conf }
    notify: restart app
    # Handler notified if ANY task in block changes

Handlers Run Once

# Even if notify is called 5 times, handler runs ONCE
- template: { src: "{{ item }}.j2", dest: "/etc/conf.d/{{ item }}" }
  loop: [a, b, c, d, e]
  notify: restart app
  # restart app runs ONCE at end, not 5 times

FAQ

When do handlers actually run?

At the end of the play (after all tasks), or when meta: flush_handlers is called.

What if the play fails before handlers run?

Notified handlers do NOT run if the play fails. Use --force-handlers or force_handlers: true in the play to override.

Can handlers notify other handlers?

Yes — a handler can include notify: to chain to another handler.

Related Articles

Jinja2 filters in Ansible templatesskipping tasks with Ansible whenAnsible Inventory GuideTLS for Nginx via Ansiblewhat Ansible roles are and how to use them

Category: troubleshooting

Browse all Ansible tutorials · AnsiblePilot Home