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

  1. A task with notify triggers the handler when the task status is changed
  2. Handlers are queued, not run immediately
  3. All handlers run once at the end of the play, in the order they're defined
  4. 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

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

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

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"

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

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

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:

  1. At the end of the play (after all tasks)
  2. In the order defined in the handlers section
  3. Once regardless of how many tasks notify them
  4. 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?

  1. The notifying task didn't report changed (was already in desired state)
  2. The task failed before reaching the handler
  3. Handler name doesn't match notify string (case-sensitive!)
  4. 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.

Category: troubleshooting

Browse all Ansible tutorials · AnsiblePilot Home