Ansible Handlers

Handlers solve a specific and common problem: certain actions should only happen when something actually changes. Restarting a web server when you deploy a new config file is correct. Restarting it when the config file was already up to date is unnecessary and causes brief downtime. Handlers implement this "restart only on change" logic elegantly and reliably.

How Handlers Work

A handler is a task defined in the handlers: section of a play. It does not run automatically — it only runs when a regular task explicitly notifies it using the notify keyword. And critically, a handler runs only once per play, even if multiple tasks notify it. This prevents the scenario where a config file task and a package install task both notify a restart handler, causing the service to restart twice.

tasks:
  - name: Install Nginx
    apt:
      name: nginx
      state: present
    notify: Restart Nginx
  
  - name: Deploy Nginx configuration
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: Restart Nginx    # Same handler notified twice - runs only once
  
  - name: Deploy SSL certificate
    copy:
      src: files/cert.pem
      dest: /etc/nginx/ssl/cert.pem
    notify: Restart Nginx    # Still runs only once

handlers:
  - name: Restart Nginx
    service:
      name: nginx
      state: restarted

In this example, even though three tasks notify the Restart Nginx handler, the service restart happens exactly once at the end of the play.

When Handlers Run

By default, all notified handlers run after all tasks in the play have completed. This is the "flush handlers at end of play" behaviour. The practical implication: if five tasks in a play notify the same handler, the handler runs once at the very end, after all five tasks complete.

To force handlers to run immediately at a specific point in the play, use the meta module:

- name: Apply config changes
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: Restart Nginx

- name: Flush handlers now before testing
  meta: flush_handlers

- name: Test Nginx responds after restart
  uri:
    url: http://localhost
    status_code: 200

Handlers with listen

The listen keyword lets multiple handlers subscribe to the same notification topic:

tasks:
  - name: Update application config
    template:
      src: app.conf.j2
      dest: /etc/app/config.yml
    notify: restart application services

handlers:
  - name: Restart App Server
    service:
      name: appserver
      state: restarted
    listen: restart application services
  
  - name: Restart Cache
    service:
      name: redis
      state: restarted
    listen: restart application services
  
  - name: Clear CDN Cache
    uri:
      url: https://cdn.example.com/purge
      method: POST
    listen: restart application services

One notification triggers all three handlers. This is useful when a configuration change requires coordinating multiple restart actions.

Handler Failure and Idempotency

If a task fails and the play aborts, notified handlers do not run by default. This can leave a service in an inconsistent state — the config was updated but the service was not restarted. The --force-handlers flag overrides this and runs handlers even when tasks fail:

ansible-playbook site.yml --force-handlers

In production playbooks, prefer placing the handler trigger in a block with rescue logic rather than relying on --force-handlers.

Handler Chaining

Handlers can notify other handlers, creating chains:

handlers:
  - name: Restart Nginx
    service:
      name: nginx
      state: restarted
    notify: Verify Nginx Startup
  
  - name: Verify Nginx Startup
    uri:
      url: http://localhost
      status_code: 200

Real-World Handler Patterns

Common patterns you will encounter in production playbooks:

  • Config deploy → reload (not restart) service — state: reloaded applies config changes without dropping connections
  • Certificate update → restart service — required for SSL changes to take effect
  • Package install → daemon reload → start service — required when installing systemd service units
  • Application deploy → clear cache — invalidate caches after new code is deployed

Try This: Handler Chain for Web Server Update

Create a playbook that deploys an Nginx config file and an SSL certificate. Use a handler chain: the config deploy notifies a "Validate Nginx Config" handler that runs nginx -t; if validation passes, it notifies a "Reload Nginx" handler. This pattern prevents service restarts with broken configurations — a critical production safety measure. Run the playbook with a valid config, then intentionally break the config and re-run to verify the safety net works.

Summary

Handlers run once per play when notified by tasks that report changes. They implement the "restart only when something changed" pattern that prevents unnecessary service disruptions. The listen keyword allows multiple handlers to respond to a single notification. meta: flush_handlers forces immediate handler execution. Handler chaining creates multi-step response sequences. Handlers are an essential idempotency tool in production Ansible playbooks.

Leave a Comment