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 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 use when 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 as Restart 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 reported ok (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 referenceAnsible conditional patternshandler ordering in Ansible

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home